tailwindcss
Advanced tools
Comparing version 0.0.0-insiders.23d3a31 to 0.0.0-insiders.245058c
import type { Config } from './types/config' | ||
declare const theme: Config['theme'] | ||
import { DefaultTheme } from './types/generated/default-theme' | ||
declare const theme: Config['theme'] & DefaultTheme | ||
export = theme |
@@ -5,8 +5,28 @@ "use strict"; | ||
}); | ||
exports.lazyPostcss = lazyPostcss; | ||
exports.lazyAutoprefixer = lazyAutoprefixer; | ||
exports.lazyCssnano = lazyCssnano; | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
lazyPostcss: function() { | ||
return lazyPostcss; | ||
}, | ||
lazyPostcssImport: function() { | ||
return lazyPostcssImport; | ||
}, | ||
lazyAutoprefixer: function() { | ||
return lazyAutoprefixer; | ||
}, | ||
lazyCssnano: function() { | ||
return lazyCssnano; | ||
} | ||
}); | ||
function lazyPostcss() { | ||
return require("postcss"); | ||
} | ||
function lazyPostcssImport() { | ||
return require("postcss-import"); | ||
} | ||
function lazyAutoprefixer() { | ||
@@ -13,0 +33,0 @@ return require("autoprefixer"); |
797
lib/cli.js
#!/usr/bin/env node | ||
"use strict"; | ||
var _indexJs = require("../peers/index.js"); | ||
var _chokidar = _interopRequireDefault(require("chokidar")); | ||
var _path = _interopRequireDefault(require("path")); | ||
var _arg = _interopRequireDefault(require("arg")); | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _postcssLoadConfig = _interopRequireDefault(require("postcss-load-config")); | ||
var _lilconfig = require("lilconfig"); | ||
var _plugins // Little bit scary, looking at private/internal API | ||
= _interopRequireDefault(require("postcss-load-config/src/plugins")); | ||
var _options // Little bit scary, looking at private/internal API | ||
= _interopRequireDefault(require("postcss-load-config/src/options")); | ||
var _processTailwindFeatures = _interopRequireDefault(require("./processTailwindFeatures")); | ||
var _resolveConfig = _interopRequireDefault(require("../resolveConfig")); | ||
var _fastGlob = _interopRequireDefault(require("fast-glob")); | ||
var _getModuleDependencies = _interopRequireDefault(require("./lib/getModuleDependencies")); | ||
var _log = _interopRequireDefault(require("./util/log")); | ||
var _packageJson = _interopRequireDefault(require("../package.json")); | ||
var _normalizePath = _interopRequireDefault(require("normalize-path")); | ||
var _validateConfigJs = require("./util/validateConfig.js"); | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
let env = { | ||
DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== "0" | ||
}; | ||
function isESM() { | ||
const pkgPath = _path.default.resolve("./package.json"); | ||
try { | ||
let pkg = JSON.parse(_fs.default.readFileSync(pkgPath, "utf8")); | ||
return pkg.type && pkg.type === "module"; | ||
} catch (err) { | ||
return false; | ||
} | ||
} | ||
let configs = isESM() ? { | ||
tailwind: "tailwind.config.cjs", | ||
postcss: "postcss.config.cjs" | ||
} : { | ||
tailwind: "tailwind.config.js", | ||
postcss: "postcss.config.js" | ||
}; | ||
// --- | ||
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 = ""; | ||
} | ||
} | ||
async function outputFile(file, contents) { | ||
if (_fs.default.existsSync(file) && await _fs.default.promises.readFile(file, "utf8") === contents) { | ||
return; // Skip writing the file | ||
} | ||
// Write the file | ||
await _fs.default.promises.writeFile(file, contents, "utf8"); | ||
} | ||
function drainStdin() { | ||
return new Promise((resolve, reject)=>{ | ||
let result = ""; | ||
process.stdin.on("data", (chunk)=>{ | ||
result += chunk; | ||
}); | ||
process.stdin.on("end", ()=>resolve(result) | ||
); | ||
process.stdin.on("error", (err)=>reject(err) | ||
); | ||
}); | ||
} | ||
function help({ message , usage , commands: commands1 , options }) { | ||
let indent = 2; | ||
// Render header | ||
console.log(); | ||
console.log(`${_packageJson.default.name} v${_packageJson.default.version}`); | ||
// Render message | ||
if (message) { | ||
console.log(); | ||
for (let msg of message.split("\n")){ | ||
console.log(msg); | ||
} | ||
} | ||
// Render usage | ||
if (usage && usage.length > 0) { | ||
console.log(); | ||
console.log("Usage:"); | ||
for (let example of usage){ | ||
console.log(" ".repeat(indent), example); | ||
} | ||
} | ||
// Render commands | ||
if (commands1 && commands1.length > 0) { | ||
console.log(); | ||
console.log("Commands:"); | ||
for (let command1 of commands1){ | ||
console.log(" ".repeat(indent), command1); | ||
} | ||
} | ||
// 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: flags1 , description , deprecated } of Object.values(groupedOptions)){ | ||
if (deprecated) continue; | ||
if (flags1.length === 1) { | ||
console.log(" ".repeat(indent + 4 /* 4 = "-i, ".length */ ), flags1.slice().reverse().join(", ").padEnd(20, " "), description); | ||
} else { | ||
console.log(" ".repeat(indent), flags1.slice().reverse().join(", ").padEnd(24, " "), description); | ||
} | ||
} | ||
} | ||
console.log(); | ||
} | ||
function oneOf(...options) { | ||
return Object.assign((value = true)=>{ | ||
for (let option of options){ | ||
let parsed = option(value); | ||
if (parsed === value) { | ||
return parsed; | ||
} | ||
} | ||
throw new Error("..."); | ||
}, { | ||
manualParsing: true | ||
}); | ||
} | ||
function loadPostcss() { | ||
// Try to load a local `postcss` version first | ||
try { | ||
return require("postcss"); | ||
} catch {} | ||
return (0, _indexJs).lazyPostcss(); | ||
} | ||
let commands = { | ||
init: { | ||
run: init, | ||
args: { | ||
"--full": { | ||
type: Boolean, | ||
description: `Initialize a full \`${configs.tailwind}\` file` | ||
}, | ||
"--postcss": { | ||
type: Boolean, | ||
description: `Initialize a \`${configs.postcss}\` file` | ||
}, | ||
"--types": { | ||
type: Boolean, | ||
description: `Add TypeScript types for the \`${configs.tailwind}\` file` | ||
}, | ||
"-f": "--full", | ||
"-p": "--postcss" | ||
} | ||
}, | ||
build: { | ||
run: build, | ||
args: { | ||
"--input": { | ||
type: String, | ||
description: "Input file" | ||
}, | ||
"--output": { | ||
type: String, | ||
description: "Output file" | ||
}, | ||
"--watch": { | ||
type: Boolean, | ||
description: "Watch for changes and rebuild as needed" | ||
}, | ||
"--poll": { | ||
type: Boolean, | ||
description: "Use polling instead of filesystem events when watching" | ||
}, | ||
"--content": { | ||
type: String, | ||
description: "Content paths to use for removing unused classes" | ||
}, | ||
"--purge": { | ||
type: String, | ||
deprecated: true | ||
}, | ||
"--postcss": { | ||
type: oneOf(String, Boolean), | ||
description: "Load custom PostCSS configuration" | ||
}, | ||
"--minify": { | ||
type: Boolean, | ||
description: "Minify the output" | ||
}, | ||
"--config": { | ||
type: String, | ||
description: "Path to a custom config file" | ||
}, | ||
"--no-autoprefixer": { | ||
type: Boolean, | ||
description: "Disable autoprefixer" | ||
}, | ||
"-c": "--config", | ||
"-i": "--input", | ||
"-o": "--output", | ||
"-m": "--minify", | ||
"-w": "--watch", | ||
"-p": "--poll" | ||
} | ||
} | ||
}; | ||
let sharedFlags = { | ||
"--help": { | ||
type: Boolean, | ||
description: "Display usage information" | ||
}, | ||
"-h": "--help" | ||
}; | ||
if (process.stdout.isTTY /* Detect redirecting output to a file */ && (process.argv[2] === undefined || process.argv.slice(2).every((flag)=>sharedFlags[flag] !== undefined | ||
))) { | ||
help({ | ||
usage: [ | ||
"tailwindcss [--input input.css] [--output output.css] [--watch] [options...]", | ||
"tailwindcss init [--full] [--postcss] [--types] [options...]", | ||
], | ||
commands: Object.keys(commands).filter((command2)=>command2 !== "build" | ||
).map((command3)=>`${command3} [options]` | ||
), | ||
options: { | ||
...commands.build.args, | ||
...sharedFlags | ||
} | ||
}); | ||
process.exit(0); | ||
} | ||
let command = ((arg = "")=>arg.startsWith("-") ? undefined : arg | ||
)(process.argv[2]) || "build"; | ||
if (commands[command] === undefined) { | ||
if (_fs.default.existsSync(_path.default.resolve(command))) { | ||
// TODO: Deprecate this in future versions | ||
// Check if non-existing command, might be a file. | ||
command = "build"; | ||
} else { | ||
help({ | ||
message: `Invalid command: ${command}`, | ||
usage: [ | ||
"tailwindcss <command> [options]" | ||
], | ||
commands: Object.keys(commands).filter((command4)=>command4 !== "build" | ||
).map((command5)=>`${command5} [options]` | ||
), | ||
options: sharedFlags | ||
}); | ||
process.exit(1); | ||
} | ||
} | ||
// Execute command | ||
let { args: flags , run } = commands[command]; | ||
let args = (()=>{ | ||
try { | ||
let result = (0, _arg).default(Object.fromEntries(Object.entries({ | ||
...flags, | ||
...sharedFlags | ||
}).filter(([_key, value])=>{ | ||
var ref; | ||
return !(value === null || value === void 0 ? void 0 : (ref = value.type) === null || ref === void 0 ? void 0 : ref.manualParsing); | ||
}).map(([key, value])=>[ | ||
key, | ||
typeof value === "object" ? value.type : value | ||
] | ||
)), { | ||
permissive: true | ||
}); | ||
// Manual parsing of flags to allow for special flags like oneOf(Boolean, String) | ||
for(let i = result["_"].length - 1; i >= 0; --i){ | ||
let flag = result["_"][i]; | ||
if (!flag.startsWith("-")) continue; | ||
let flagName = flag; | ||
let handler = flags[flag]; | ||
// Resolve flagName & handler | ||
while(typeof handler === "string"){ | ||
flagName = handler; | ||
handler = flags[handler]; | ||
} | ||
if (!handler) continue; | ||
let args1 = []; | ||
let offset = i + 1; | ||
// Parse args for current flag | ||
while(result["_"][offset] && !result["_"][offset].startsWith("-")){ | ||
args1.push(result["_"][offset++]); | ||
} | ||
// Cleanup manually parsed flags + args | ||
result["_"].splice(i, 1 + args1.length); | ||
// Set the resolved value in the `result` object | ||
result[flagName] = handler.type(args1.length === 0 ? undefined : args1.length === 1 ? args1[0] : args1, flagName); | ||
} | ||
// Ensure that the `command` is always the first argument in the `args`. | ||
// This is important so that we don't have to check if a default command | ||
// (build) was used or not from within each plugin. | ||
// | ||
// E.g.: tailwindcss input.css -> _: ['build', 'input.css'] | ||
// E.g.: tailwindcss build input.css -> _: ['build', 'input.css'] | ||
if (result["_"][0] !== command) { | ||
result["_"].unshift(command); | ||
} | ||
return result; | ||
} catch (err) { | ||
if (err.code === "ARG_UNKNOWN_OPTION") { | ||
help({ | ||
message: err.message, | ||
usage: [ | ||
"tailwindcss <command> [options]" | ||
], | ||
options: sharedFlags | ||
}); | ||
process.exit(1); | ||
} | ||
throw err; | ||
} | ||
})(); | ||
if (args["--help"]) { | ||
help({ | ||
options: { | ||
...flags, | ||
...sharedFlags | ||
}, | ||
usage: [ | ||
`tailwindcss ${command} [options]` | ||
] | ||
}); | ||
process.exit(0); | ||
} | ||
run(); | ||
// --- | ||
function init() { | ||
let messages = []; | ||
var ref; | ||
let tailwindConfigLocation = _path.default.resolve((ref = args["_"][1]) !== null && ref !== void 0 ? ref : `./${configs.tailwind}`); | ||
if (_fs.default.existsSync(tailwindConfigLocation)) { | ||
messages.push(`${_path.default.basename(tailwindConfigLocation)} already exists.`); | ||
} else { | ||
let stubFile = _fs.default.readFileSync(args["--full"] ? _path.default.resolve(__dirname, "../stubs/defaultConfig.stub.js") : _path.default.resolve(__dirname, "../stubs/simpleConfig.stub.js"), "utf8"); | ||
if (args["--types"]) { | ||
let typesHeading = "/** @type {import('tailwindcss/types').Config} */"; | ||
stubFile = stubFile.replace(`module.exports = `, `${typesHeading}\nconst config = `) + "\nmodule.exports = config"; | ||
} | ||
// Change colors import | ||
stubFile = stubFile.replace("../colors", "tailwindcss/colors"); | ||
_fs.default.writeFileSync(tailwindConfigLocation, stubFile, "utf8"); | ||
messages.push(`Created Tailwind CSS config file: ${_path.default.basename(tailwindConfigLocation)}`); | ||
} | ||
if (args["--postcss"]) { | ||
let postcssConfigLocation = _path.default.resolve(`./${configs.postcss}`); | ||
if (_fs.default.existsSync(postcssConfigLocation)) { | ||
messages.push(`${_path.default.basename(postcssConfigLocation)} already exists.`); | ||
} else { | ||
let stubFile = _fs.default.readFileSync(_path.default.resolve(__dirname, "../stubs/defaultPostCssConfig.stub.js"), "utf8"); | ||
_fs.default.writeFileSync(postcssConfigLocation, stubFile, "utf8"); | ||
messages.push(`Created PostCSS config file: ${_path.default.basename(postcssConfigLocation)}`); | ||
} | ||
} | ||
if (messages.length > 0) { | ||
console.log(); | ||
for (let message of messages){ | ||
console.log(message); | ||
} | ||
} | ||
} | ||
async function build() { | ||
let input = args["--input"]; | ||
let output = args["--output"]; | ||
let shouldWatch = args["--watch"]; | ||
let shouldPoll = args["--poll"]; | ||
let shouldCoalesceWriteEvents = shouldPoll || process.platform === "win32"; | ||
let includePostCss = args["--postcss"]; | ||
// Polling interval in milliseconds | ||
// Used only when polling or coalescing add/change events on Windows | ||
let pollInterval = 10; | ||
// TODO: Deprecate this in future versions | ||
if (!input && args["_"][1]) { | ||
console.error("[deprecation] Running tailwindcss without -i, please provide an input file."); | ||
input = args["--input"] = args["_"][1]; | ||
} | ||
if (input && input !== "-" && !_fs.default.existsSync(input = _path.default.resolve(input))) { | ||
console.error(`Specified input file ${args["--input"]} does not exist.`); | ||
process.exit(9); | ||
} | ||
if (args["--config"] && !_fs.default.existsSync(args["--config"] = _path.default.resolve(args["--config"]))) { | ||
console.error(`Specified config file ${args["--config"]} does not exist.`); | ||
process.exit(9); | ||
} | ||
let configPath = args["--config"] ? args["--config"] : ((defaultPath)=>_fs.default.existsSync(defaultPath) ? defaultPath : null | ||
)(_path.default.resolve(`./${configs.tailwind}`)); | ||
async function loadPostCssPlugins() { | ||
let customPostCssPath = typeof args["--postcss"] === "string" ? args["--postcss"] : undefined; | ||
let config1 = customPostCssPath ? await (async ()=>{ | ||
let file = _path.default.resolve(customPostCssPath); | ||
// Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js | ||
let { config ={} } = await (0, _lilconfig).lilconfig("postcss").load(file); | ||
if (typeof config === "function") { | ||
config = config(); | ||
} else { | ||
config = Object.assign({}, config); | ||
} | ||
if (!config.plugins) { | ||
config.plugins = []; | ||
} | ||
return { | ||
file, | ||
plugins: (0, _plugins).default(config, file), | ||
options: (0, _options).default(config, file) | ||
}; | ||
})() : await (0, _postcssLoadConfig).default(); | ||
let configPlugins = config1.plugins; | ||
let configPluginTailwindIdx = configPlugins.findIndex((plugin)=>{ | ||
if (typeof plugin === "function" && plugin.name === "tailwindcss") { | ||
return true; | ||
} | ||
if (typeof plugin === "object" && plugin !== null && plugin.postcssPlugin === "tailwindcss") { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
let beforePlugins = configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx); | ||
let afterPlugins = configPluginTailwindIdx === -1 ? configPlugins : configPlugins.slice(configPluginTailwindIdx + 1); | ||
return [ | ||
beforePlugins, | ||
afterPlugins, | ||
config1.options | ||
]; | ||
} | ||
function resolveConfig() { | ||
let config = configPath ? require(configPath) : {}; | ||
if (args["--purge"]) { | ||
_log.default.warn("purge-flag-deprecated", [ | ||
"The `--purge` flag has been deprecated.", | ||
"Please use `--content` instead.", | ||
]); | ||
if (!args["--content"]) { | ||
args["--content"] = args["--purge"]; | ||
} | ||
} | ||
if (args["--content"]) { | ||
let files = args["--content"].split(/(?<!{[^}]+),/); | ||
let resolvedConfig = (0, _resolveConfig).default(config, { | ||
content: { | ||
files | ||
} | ||
}); | ||
resolvedConfig.content.files = files; | ||
resolvedConfig = (0, _validateConfigJs).validateConfig(resolvedConfig); | ||
return resolvedConfig; | ||
} | ||
let resolvedConfig = (0, _resolveConfig).default(config); | ||
resolvedConfig = (0, _validateConfigJs).validateConfig(resolvedConfig); | ||
return resolvedConfig; | ||
} | ||
function extractFileGlobs(config) { | ||
return config.content.files.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"; | ||
}).map((glob)=>(0, _normalizePath).default(glob) | ||
); | ||
} | ||
function extractRawContent(config) { | ||
return config.content.files.filter((file)=>{ | ||
return typeof file === "object" && file !== null; | ||
}); | ||
} | ||
function getChangedContent(config) { | ||
let changedContent = []; | ||
// Resolve globs from the content config | ||
let globs = extractFileGlobs(config); | ||
let files = _fastGlob.default.sync(globs); | ||
for (let file of files){ | ||
changedContent.push({ | ||
content: _fs.default.readFileSync(_path.default.resolve(file), "utf8"), | ||
extension: _path.default.extname(file).slice(1) | ||
}); | ||
} | ||
// Resolve raw content in the tailwind config | ||
for (let { raw: content , extension ="html" } of extractRawContent(config)){ | ||
changedContent.push({ | ||
content, | ||
extension | ||
}); | ||
} | ||
return changedContent; | ||
} | ||
async function buildOnce() { | ||
let config = resolveConfig(); | ||
let changedContent = getChangedContent(config); | ||
let tailwindPlugin = ()=>{ | ||
return { | ||
postcssPlugin: "tailwindcss", | ||
Once (root, { result }) { | ||
(0, _processTailwindFeatures).default(({ createContext })=>{ | ||
return ()=>{ | ||
return createContext(config, changedContent); | ||
}; | ||
})(root, result); | ||
} | ||
}; | ||
}; | ||
tailwindPlugin.postcss = true; | ||
let [beforePlugins, afterPlugins, postcssOptions] = includePostCss ? await loadPostCssPlugins() : [ | ||
[], | ||
[], | ||
{} | ||
]; | ||
let plugins = [ | ||
...beforePlugins, | ||
tailwindPlugin, | ||
!args["--minify"] && formatNodes, | ||
...afterPlugins, | ||
!args["--no-autoprefixer"] && (()=>{ | ||
// Try to load a local `autoprefixer` version first | ||
try { | ||
return require("autoprefixer"); | ||
} catch {} | ||
return (0, _indexJs).lazyAutoprefixer(); | ||
})(), | ||
args["--minify"] && (()=>{ | ||
let options = { | ||
preset: [ | ||
"default", | ||
{ | ||
cssDeclarationSorter: false | ||
} | ||
] | ||
}; | ||
// Try to load a local `cssnano` version first | ||
try { | ||
return require("cssnano"); | ||
} catch {} | ||
return (0, _indexJs).lazyCssnano()(options); | ||
})(), | ||
].filter(Boolean); | ||
let postcss = loadPostcss(); | ||
let processor = postcss(plugins); | ||
function processCSS(css) { | ||
let start = process.hrtime.bigint(); | ||
return Promise.resolve().then(()=>output ? _fs.default.promises.mkdir(_path.default.dirname(output), { | ||
recursive: true | ||
}) : null | ||
).then(()=>processor.process(css, { | ||
...postcssOptions, | ||
from: input, | ||
to: output | ||
}) | ||
).then((result)=>{ | ||
if (!output) { | ||
return process.stdout.write(result.css); | ||
} | ||
return Promise.all([ | ||
outputFile(output, result.css), | ||
result.map && outputFile(output + ".map", result.map.toString()), | ||
].filter(Boolean)); | ||
}).then(()=>{ | ||
let end = process.hrtime.bigint(); | ||
console.error(); | ||
console.error("Done in", (end - start) / BigInt(1000000) + "ms."); | ||
}); | ||
} | ||
let css1 = await (()=>{ | ||
// Piping in data, let's drain the stdin | ||
if (input === "-") { | ||
return drainStdin(); | ||
} | ||
// Input file has been provided | ||
if (input) { | ||
return _fs.default.readFileSync(_path.default.resolve(input), "utf8"); | ||
} | ||
// No input file provided, fallback to default atrules | ||
return "@tailwind base; @tailwind components; @tailwind utilities"; | ||
})(); | ||
return processCSS(css1); | ||
} | ||
let context = null; | ||
async function startWatcher() { | ||
let changedContent = []; | ||
let configDependencies = []; | ||
let contextDependencies = new Set(); | ||
let watcher = null; | ||
function refreshConfig() { | ||
env.DEBUG && console.time("Module dependencies"); | ||
for (let file1 of configDependencies){ | ||
delete require.cache[require.resolve(file1)]; | ||
} | ||
if (configPath) { | ||
configDependencies = (0, _getModuleDependencies).default(configPath).map(({ file })=>file | ||
); | ||
for (let dependency of configDependencies){ | ||
contextDependencies.add(dependency); | ||
} | ||
} | ||
env.DEBUG && console.timeEnd("Module dependencies"); | ||
return resolveConfig(); | ||
} | ||
let [beforePlugins, afterPlugins] = includePostCss ? await loadPostCssPlugins() : [ | ||
[], | ||
[] | ||
]; | ||
let plugins = [ | ||
...beforePlugins, | ||
"__TAILWIND_PLUGIN_POSITION__", | ||
!args["--minify"] && formatNodes, | ||
...afterPlugins, | ||
!args["--no-autoprefixer"] && (()=>{ | ||
// Try to load a local `autoprefixer` version first | ||
try { | ||
return require("autoprefixer"); | ||
} catch {} | ||
return (0, _indexJs).lazyAutoprefixer(); | ||
})(), | ||
args["--minify"] && (()=>{ | ||
let options = { | ||
preset: [ | ||
"default", | ||
{ | ||
cssDeclarationSorter: false | ||
} | ||
] | ||
}; | ||
// Try to load a local `cssnano` version first | ||
try { | ||
return require("cssnano"); | ||
} catch {} | ||
return (0, _indexJs).lazyCssnano()(options); | ||
})(), | ||
].filter(Boolean); | ||
async function rebuild(config) { | ||
env.DEBUG && console.time("Finished in"); | ||
let tailwindPlugin = ()=>{ | ||
return { | ||
postcssPlugin: "tailwindcss", | ||
Once (root, { result }) { | ||
env.DEBUG && console.time("Compiling CSS"); | ||
(0, _processTailwindFeatures).default(({ createContext })=>{ | ||
console.error(); | ||
console.error("Rebuilding..."); | ||
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"); | ||
} | ||
}; | ||
}; | ||
tailwindPlugin.postcss = true; | ||
let tailwindPluginIdx = plugins.indexOf("__TAILWIND_PLUGIN_POSITION__"); | ||
let copy = plugins.slice(); | ||
copy.splice(tailwindPluginIdx, 1, tailwindPlugin); | ||
let postcss = loadPostcss(); | ||
let processor = postcss(copy); | ||
function processCSS(css) { | ||
let start = process.hrtime.bigint(); | ||
return Promise.resolve().then(()=>output ? _fs.default.promises.mkdir(_path.default.dirname(output), { | ||
recursive: true | ||
}) : null | ||
).then(()=>processor.process(css, { | ||
from: input, | ||
to: output | ||
}) | ||
).then(async (result)=>{ | ||
for (let message of result.messages){ | ||
if (message.type === "dependency") { | ||
contextDependencies.add(message.file); | ||
} | ||
} | ||
watcher.add([ | ||
...contextDependencies | ||
]); | ||
if (!output) { | ||
return process.stdout.write(result.css); | ||
} | ||
return Promise.all([ | ||
outputFile(output, result.css), | ||
result.map && outputFile(output + ".map", result.map.toString()), | ||
].filter(Boolean)); | ||
}).then(()=>{ | ||
let end = process.hrtime.bigint(); | ||
console.error("Done in", (end - start) / BigInt(1000000) + "ms."); | ||
}).catch((err)=>{ | ||
if (err.name === "CssSyntaxError") { | ||
console.error(err.toString()); | ||
} else { | ||
console.error(err); | ||
} | ||
}); | ||
} | ||
let css2 = await (()=>{ | ||
// Piping in data, let's drain the stdin | ||
if (input === "-") { | ||
return drainStdin(); | ||
} | ||
// Input file has been provided | ||
if (input) { | ||
return _fs.default.readFileSync(_path.default.resolve(input), "utf8"); | ||
} | ||
// No input file provided, fallback to default atrules | ||
return "@tailwind base; @tailwind components; @tailwind utilities"; | ||
})(); | ||
let result1 = await processCSS(css2); | ||
env.DEBUG && console.timeEnd("Finished in"); | ||
return result1; | ||
} | ||
let config2 = refreshConfig(configPath); | ||
if (input) { | ||
contextDependencies.add(_path.default.resolve(input)); | ||
} | ||
watcher = _chokidar.default.watch([ | ||
...contextDependencies, | ||
...extractFileGlobs(config2) | ||
], { | ||
usePolling: shouldPoll, | ||
interval: shouldPoll ? pollInterval : undefined, | ||
ignoreInitial: true, | ||
awaitWriteFinish: shouldCoalesceWriteEvents ? { | ||
stabilityThreshold: 50, | ||
pollInterval: pollInterval | ||
} : false | ||
}); | ||
let chain = Promise.resolve(); | ||
watcher.on("change", async (file)=>{ | ||
if (contextDependencies.has(file)) { | ||
env.DEBUG && console.time("Resolve config"); | ||
context = null; | ||
config2 = refreshConfig(configPath); | ||
env.DEBUG && console.timeEnd("Resolve config"); | ||
env.DEBUG && console.time("Watch new files"); | ||
let globs = extractFileGlobs(config2); | ||
watcher.add(configDependencies); | ||
watcher.add(globs); | ||
env.DEBUG && console.timeEnd("Watch new files"); | ||
chain = chain.then(async ()=>{ | ||
changedContent.push(...getChangedContent(config2)); | ||
await rebuild(config2); | ||
}); | ||
} else { | ||
chain = chain.then(async ()=>{ | ||
changedContent.push({ | ||
content: _fs.default.readFileSync(_path.default.resolve(file), "utf8"), | ||
extension: _path.default.extname(file).slice(1) | ||
}); | ||
await rebuild(config2); | ||
}); | ||
} | ||
}); | ||
watcher.on("add", async (file)=>{ | ||
chain = chain.then(async ()=>{ | ||
changedContent.push({ | ||
content: _fs.default.readFileSync(_path.default.resolve(file), "utf8"), | ||
extension: _path.default.extname(file).slice(1) | ||
}); | ||
await rebuild(config2); | ||
}); | ||
}); | ||
chain = chain.then(()=>{ | ||
changedContent.push(...getChangedContent(config2)); | ||
return rebuild(config2); | ||
}); | ||
} | ||
if (shouldWatch) { | ||
/* Abort the watcher if stdin is closed to avoid zombie processes */ process.stdin.on("end", ()=>process.exit(0) | ||
); | ||
process.stdin.resume(); | ||
startWatcher(); | ||
} else { | ||
buildOnce(); | ||
} | ||
} | ||
module.exports = require("./cli/index"); |
@@ -5,4 +5,9 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
var _default = [ | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _default = [ | ||
"preflight", | ||
@@ -28,4 +33,6 @@ "container", | ||
"boxSizing", | ||
"lineClamp", | ||
"display", | ||
"aspectRatio", | ||
"size", | ||
"height", | ||
@@ -42,2 +49,3 @@ "maxHeight", | ||
"tableLayout", | ||
"captionSide", | ||
"borderCollapse", | ||
@@ -63,2 +71,3 @@ "borderSpacing", | ||
"listStyleType", | ||
"listStyleImage", | ||
"appearance", | ||
@@ -95,3 +104,5 @@ "columns", | ||
"textOverflow", | ||
"hyphens", | ||
"whitespace", | ||
"textWrap", | ||
"wordBreak", | ||
@@ -182,4 +193,5 @@ "borderRadius", | ||
"willChange", | ||
"content" | ||
"contain", | ||
"content", | ||
"forcedColorAdjust" | ||
]; | ||
exports.default = _default; |
@@ -5,8 +5,22 @@ "use strict"; | ||
}); | ||
exports.flagEnabled = flagEnabled; | ||
exports.issueFlagNotices = issueFlagNotices; | ||
exports.default = void 0; | ||
var _picocolors = _interopRequireDefault(require("picocolors")); | ||
var _log = _interopRequireDefault(require("./util/log")); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
flagEnabled: function() { | ||
return flagEnabled; | ||
}, | ||
issueFlagNotices: function() { | ||
return issueFlagNotices; | ||
}, | ||
default: function() { | ||
return _default; | ||
} | ||
}); | ||
const _picocolors = /*#__PURE__*/ _interop_require_default(require("picocolors")); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("./util/log")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -17,8 +31,17 @@ default: obj | ||
let defaults = { | ||
optimizeUniversalDefaults: false | ||
optimizeUniversalDefaults: false, | ||
generalizedModifiers: true, | ||
disableColorOpacityUtilitiesByDefault: false, | ||
relativeContentPathsByDefault: false | ||
}; | ||
let featureFlags = { | ||
future: [], | ||
future: [ | ||
"hoverOnlyWhenSupported", | ||
"respectDefaultRingColorOpacity", | ||
"disableColorOpacityUtilitiesByDefault", | ||
"relativeContentPathsByDefault" | ||
], | ||
experimental: [ | ||
"optimizeUniversalDefaults" | ||
"optimizeUniversalDefaults", | ||
"generalizedModifiers" | ||
] | ||
@@ -28,10 +51,10 @@ }; | ||
if (featureFlags.future.includes(flag)) { | ||
var ref; | ||
var ref1, ref2; | ||
return config.future === "all" || ((ref2 = (ref1 = config === null || config === void 0 ? void 0 : (ref = config.future) === null || ref === void 0 ? void 0 : ref[flag]) !== null && ref1 !== void 0 ? ref1 : defaults[flag]) !== null && ref2 !== void 0 ? ref2 : false); | ||
var _config_future; | ||
var _config_future_flag, _ref; | ||
return config.future === "all" || ((_ref = (_config_future_flag = config === null || config === void 0 ? void 0 : (_config_future = config.future) === null || _config_future === void 0 ? void 0 : _config_future[flag]) !== null && _config_future_flag !== void 0 ? _config_future_flag : defaults[flag]) !== null && _ref !== void 0 ? _ref : false); | ||
} | ||
if (featureFlags.experimental.includes(flag)) { | ||
var ref3; | ||
var ref4, ref5; | ||
return config.experimental === "all" || ((ref5 = (ref4 = config === null || config === void 0 ? void 0 : (ref3 = config.experimental) === null || ref3 === void 0 ? void 0 : ref3[flag]) !== null && ref4 !== void 0 ? ref4 : defaults[flag]) !== null && ref5 !== void 0 ? ref5 : false); | ||
var _config_experimental; | ||
var _config_experimental_flag, _ref1; | ||
return config.experimental === "all" || ((_ref1 = (_config_experimental_flag = config === null || config === void 0 ? void 0 : (_config_experimental = config.experimental) === null || _config_experimental === void 0 ? void 0 : _config_experimental[flag]) !== null && _config_experimental_flag !== void 0 ? _config_experimental_flag : defaults[flag]) !== null && _ref1 !== void 0 ? _ref1 : false); | ||
} | ||
@@ -44,5 +67,4 @@ return false; | ||
} | ||
var ref; | ||
return Object.keys((ref = config === null || config === void 0 ? void 0 : config.experimental) !== null && ref !== void 0 ? ref : {}).filter((flag)=>featureFlags.experimental.includes(flag) && config.experimental[flag] | ||
); | ||
var _config_experimental; | ||
return Object.keys((_config_experimental = config === null || config === void 0 ? void 0 : config.experimental) !== null && _config_experimental !== void 0 ? _config_experimental : {}).filter((flag)=>featureFlags.experimental.includes(flag) && config.experimental[flag]); | ||
} | ||
@@ -54,11 +76,9 @@ function issueFlagNotices(config) { | ||
if (experimentalFlagsEnabled(config).length > 0) { | ||
let changes = experimentalFlagsEnabled(config).map((s)=>_picocolors.default.yellow(s) | ||
).join(", "); | ||
let changes = experimentalFlagsEnabled(config).map((s)=>_picocolors.default.yellow(s)).join(", "); | ||
_log.default.warn("experimental-flags-enabled", [ | ||
`You have enabled experimental features: ${changes}`, | ||
"Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time.", | ||
"Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time." | ||
]); | ||
} | ||
} | ||
var _default = featureFlags; | ||
exports.default = _default; | ||
const _default = featureFlags; |
"use strict"; | ||
var _setupTrackingContext = _interopRequireDefault(require("./lib/setupTrackingContext")); | ||
var _processTailwindFeatures = _interopRequireDefault(require("./processTailwindFeatures")); | ||
var _sharedState = require("./lib/sharedState"); | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
module.exports = function tailwindcss(configOrPath) { | ||
return { | ||
postcssPlugin: "tailwindcss", | ||
plugins: [ | ||
_sharedState.env.DEBUG && function(root) { | ||
console.log("\n"); | ||
console.time("JIT TOTAL"); | ||
return root; | ||
}, | ||
function(root, result) { | ||
let context = (0, _setupTrackingContext).default(configOrPath); | ||
if (root.type === "document") { | ||
let roots = root.nodes.filter((node)=>node.type === "root" | ||
); | ||
for (const root1 of roots){ | ||
if (root1.type === "root") { | ||
(0, _processTailwindFeatures).default(context)(root1, result); | ||
} | ||
} | ||
return; | ||
} | ||
(0, _processTailwindFeatures).default(context)(root, result); | ||
}, | ||
_sharedState.env.DEBUG && function(root) { | ||
console.timeEnd("JIT TOTAL"); | ||
console.log("\n"); | ||
return root; | ||
}, | ||
].filter(Boolean) | ||
}; | ||
}; | ||
module.exports.postcss = true; | ||
module.exports = require("./plugin"); |
@@ -5,6 +5,11 @@ "use strict"; | ||
}); | ||
exports.hasContentChanged = hasContentChanged; | ||
var _crypto = _interopRequireDefault(require("crypto")); | ||
var sharedState = _interopRequireWildcard(require("./sharedState")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "hasContentChanged", { | ||
enumerable: true, | ||
get: function() { | ||
return hasContentChanged; | ||
} | ||
}); | ||
const _crypto = /*#__PURE__*/ _interop_require_default(require("crypto")); | ||
const _sharedState = /*#__PURE__*/ _interop_require_wildcard(require("./sharedState")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -14,22 +19,40 @@ default: obj | ||
} | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interop_require_wildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
} | ||
@@ -66,7 +89,7 @@ /** | ||
} | ||
let existingHash = sharedState.sourceHashMap.get(sourcePath); | ||
let existingHash = _sharedState.sourceHashMap.get(sourcePath); | ||
let rootHash = getHash(css); | ||
let didChange = existingHash !== rootHash; | ||
sharedState.sourceHashMap.set(sourcePath, rootHash); | ||
_sharedState.sourceHashMap.set(sourcePath, rootHash); | ||
return didChange; | ||
} |
@@ -5,3 +5,18 @@ "use strict"; | ||
}); | ||
exports.default = collapseAdjacentRules; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return collapseAdjacentRules; | ||
} | ||
}); | ||
let comparisonMap = { | ||
atrule: [ | ||
"name", | ||
"params" | ||
], | ||
rule: [ | ||
"selector" | ||
] | ||
}; | ||
let types = new Set(Object.keys(comparisonMap)); | ||
function collapseAdjacentRules() { | ||
@@ -20,7 +35,6 @@ function collapseRulesIn(root) { | ||
let properties = comparisonMap[node.type]; | ||
var _property, _property1; | ||
var _node_property, _currentRule_property; | ||
if (node.type === "atrule" && node.name === "font-face") { | ||
currentRule = node; | ||
} else if (properties.every((property)=>((_property = node[property]) !== null && _property !== void 0 ? _property : "").replace(/\s+/g, " ") === ((_property1 = currentRule[property]) !== null && _property1 !== void 0 ? _property1 : "").replace(/\s+/g, " ") | ||
)) { | ||
} else if (properties.every((property)=>((_node_property = node[property]) !== null && _node_property !== void 0 ? _node_property : "").replace(/\s+/g, " ") === ((_currentRule_property = currentRule[property]) !== null && _currentRule_property !== void 0 ? _currentRule_property : "").replace(/\s+/g, " "))) { | ||
// An AtRule may not have children (for example if we encounter duplicate @import url(…) rules) | ||
@@ -50,11 +64,1 @@ if (node.nodes) { | ||
} | ||
let comparisonMap = { | ||
atrule: [ | ||
"name", | ||
"params" | ||
], | ||
rule: [ | ||
"selector" | ||
] | ||
}; | ||
let types = new Set(Object.keys(comparisonMap)); |
@@ -5,3 +5,8 @@ "use strict"; | ||
}); | ||
exports.default = collapseDuplicateDeclarations; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return collapseDuplicateDeclarations; | ||
} | ||
}); | ||
function collapseDuplicateDeclarations() { | ||
@@ -45,4 +50,4 @@ return (root)=>{ | ||
// already seen so far. | ||
for (let decl1 of droppable){ | ||
decl1.remove(); | ||
for (let decl of droppable){ | ||
decl.remove(); | ||
} | ||
@@ -63,5 +68,5 @@ // Analyze the declarations based on its unit, drop all the declarations | ||
} | ||
for (let declarations1 of byUnit.values()){ | ||
for (let declarations of byUnit.values()){ | ||
// Get all but the last one | ||
let removableDeclarations = Array.from(declarations1).slice(0, -1); | ||
let removableDeclarations = Array.from(declarations).slice(0, -1); | ||
for (let decl of removableDeclarations){ | ||
@@ -79,6 +84,6 @@ decl.remove(); | ||
if (result) { | ||
var ref; | ||
return (ref = result[1]) !== null && ref !== void 0 ? ref : UNITLESS_NUMBER; | ||
var _result_; | ||
return (_result_ = result[1]) !== null && _result_ !== void 0 ? _result_ : UNITLESS_NUMBER; | ||
} | ||
return null; | ||
} |
@@ -5,24 +5,48 @@ "use strict"; | ||
}); | ||
exports.defaultExtractor = defaultExtractor; | ||
var regex = _interopRequireWildcard(require("./regex")); | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
Object.defineProperty(exports, "defaultExtractor", { | ||
enumerable: true, | ||
get: function() { | ||
return defaultExtractor; | ||
} | ||
}); | ||
const _regex = /*#__PURE__*/ _interop_require_wildcard(require("./regex")); | ||
const _splitAtTopLevelOnly = require("../util/splitAtTopLevelOnly"); | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interop_require_wildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
} | ||
@@ -36,7 +60,29 @@ function defaultExtractor(context) { | ||
for (let pattern of patterns){ | ||
var ref; | ||
results.push(...(ref = content.match(pattern)) !== null && ref !== void 0 ? ref : []); | ||
var _content_match; | ||
for (let result of (_content_match = content.match(pattern)) !== null && _content_match !== void 0 ? _content_match : []){ | ||
results.push(clipAtBalancedParens(result)); | ||
} | ||
} | ||
return results.filter((v)=>v !== undefined | ||
).map(clipAtBalancedParens); | ||
// Extract any subclasses from languages like Slim and Pug, eg: | ||
// div.flex.px-5.underline | ||
for (let result of results.slice()){ | ||
let segments = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(result, "."); | ||
for(let idx = 0; idx < segments.length; idx++){ | ||
let segment = segments[idx]; | ||
if (idx >= segments.length - 1) { | ||
results.push(segment); | ||
continue; | ||
} | ||
// If the next segment is a number, discard both, for example seeing | ||
// `px-1` and `5` means the real candidate was `px-1.5` which is already | ||
// captured. | ||
let next = Number(segments[idx + 1]); | ||
if (isNaN(next)) { | ||
results.push(segment); | ||
} else { | ||
idx++; | ||
} | ||
} | ||
} | ||
return results; | ||
}; | ||
@@ -46,51 +92,108 @@ } | ||
let separator = context.tailwindConfig.separator; | ||
yield regex.pattern([ | ||
// Variants | ||
"((?=((", | ||
regex.any([ | ||
regex.pattern([ | ||
/([^\s"'\[\\]+-)?\[[^\s"'\\]+\]/, | ||
separator | ||
let prefix = context.tailwindConfig.prefix !== "" ? _regex.optional(_regex.pattern([ | ||
/-?/, | ||
_regex.escape(context.tailwindConfig.prefix) | ||
])) : ""; | ||
let utility = _regex.any([ | ||
// Arbitrary properties (without square brackets) | ||
/\[[^\s:'"`]+:[^\s\[\]]+\]/, | ||
// Arbitrary properties with balanced square brackets | ||
// This is a targeted fix to continue to allow theme() | ||
// with square brackets to work in arbitrary properties | ||
// while fixing a problem with the regex matching too much | ||
/\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/, | ||
// Utilities | ||
_regex.pattern([ | ||
// Utility Name / Group Name | ||
_regex.any([ | ||
/-?(?:\w+)/, | ||
// This is here to make sure @container supports everything that other utilities do | ||
/@(?:\w+)/ | ||
]), | ||
regex.pattern([ | ||
/[^\s"'\[\\]+/, | ||
separator | ||
]), | ||
], true), | ||
")+))\\2)?", | ||
// Important (optional) | ||
/!?/, | ||
regex.any([ | ||
// Arbitrary properties | ||
/\[[^\s:'"]+:[^\s\]]+\]/, | ||
// Utilities | ||
regex.pattern([ | ||
// Utility Name / Group Name | ||
/-?(?:\w+)/, | ||
// Normal/Arbitrary values | ||
regex.optional(regex.any([ | ||
regex.pattern([ | ||
// Arbitrary values | ||
/-\[[^\s:]+\]/, | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"\\$]*)?/, | ||
// Normal/Arbitrary values | ||
_regex.optional(_regex.any([ | ||
_regex.pattern([ | ||
// Arbitrary values | ||
_regex.any([ | ||
/-(?:\w+-)*\['[^\s]+'\]/, | ||
/-(?:\w+-)*\["[^\s]+"\]/, | ||
/-(?:\w+-)*\[`[^\s]+`\]/, | ||
/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/ | ||
]), | ||
regex.pattern([ | ||
// Arbitrary values | ||
/-\[[^\s]+\]/, | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"\\$]*)?/, | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"`\\><$]*)?/ | ||
]), | ||
_regex.pattern([ | ||
// Arbitrary values | ||
_regex.any([ | ||
/-(?:\w+-)*\['[^\s]+'\]/, | ||
/-(?:\w+-)*\["[^\s]+"\]/, | ||
/-(?:\w+-)*\[`[^\s]+`\]/, | ||
/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/ | ||
]), | ||
// Normal values w/o quotes — may include an opacity modifier | ||
/[-\/][^\s'"\\$={]*/, | ||
])), | ||
]), | ||
]), | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"`\\$]*)?/ | ||
]), | ||
// Normal values w/o quotes — may include an opacity modifier | ||
/[-\/][^\s'"`\\$={><]*/ | ||
])) | ||
]) | ||
]); | ||
// 5. Inner matches | ||
// yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g | ||
let variantPatterns = [ | ||
// Without quotes | ||
_regex.any([ | ||
// This is here to provide special support for the `@` variant | ||
_regex.pattern([ | ||
/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, | ||
separator | ||
]), | ||
// With variant modifier (e.g.: group-[..]/modifier) | ||
_regex.pattern([ | ||
/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/[\w_-]+/, | ||
separator | ||
]), | ||
_regex.pattern([ | ||
/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, | ||
separator | ||
]), | ||
_regex.pattern([ | ||
/[^\s"'`\[\\]+/, | ||
separator | ||
]) | ||
]), | ||
// With quotes allowed | ||
_regex.any([ | ||
// With variant modifier (e.g.: group-[..]/modifier) | ||
_regex.pattern([ | ||
/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/[\w_-]+/, | ||
separator | ||
]), | ||
_regex.pattern([ | ||
/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, | ||
separator | ||
]), | ||
_regex.pattern([ | ||
/[^\s`\[\\]+/, | ||
separator | ||
]) | ||
]) | ||
]; | ||
for (const variantPattern of variantPatterns){ | ||
yield _regex.pattern([ | ||
// Variants | ||
"((?=((", | ||
variantPattern, | ||
")+))\\2)?", | ||
// Important (optional) | ||
/!?/, | ||
prefix, | ||
utility | ||
]); | ||
} | ||
// 5. Inner matches | ||
yield /[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g; | ||
} | ||
@@ -133,7 +236,6 @@ // We want to capture any "special" characters | ||
0: group | ||
}) | ||
); | ||
})); | ||
}); | ||
for (let match1 of matches){ | ||
let char = match1[0]; | ||
for (let match of matches){ | ||
let char = match[0]; | ||
let inStringType = openStringTypes[openStringTypes.length - 1]; | ||
@@ -158,3 +260,3 @@ if (char === inStringType) { | ||
if (depth < 0) { | ||
return input.substring(0, match1.index); | ||
return input.substring(0, match.index - 1); | ||
} | ||
@@ -166,3 +268,3 @@ // We've finished balancing the brackets but there still may be characters that can be included | ||
if (depth === 0 && !ALLOWED_CLASS_CHARACTERS.test(char)) { | ||
return input.substring(0, match1.index); | ||
return input.substring(0, match.index); | ||
} | ||
@@ -169,0 +271,0 @@ } |
@@ -5,11 +5,19 @@ "use strict"; | ||
}); | ||
exports.default = _default; | ||
var _dlv = _interopRequireDefault(require("dlv")); | ||
var _didyoumean = _interopRequireDefault(require("didyoumean")); | ||
var _transformThemeValue = _interopRequireDefault(require("../util/transformThemeValue")); | ||
var _postcssValueParser = _interopRequireDefault(require("postcss-value-parser")); | ||
var _normalizeScreens = require("../util/normalizeScreens"); | ||
var _buildMediaQuery = _interopRequireDefault(require("../util/buildMediaQuery")); | ||
var _toPath = require("../util/toPath"); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _dlv = /*#__PURE__*/ _interop_require_default(require("dlv")); | ||
const _didyoumean = /*#__PURE__*/ _interop_require_default(require("didyoumean")); | ||
const _transformThemeValue = /*#__PURE__*/ _interop_require_default(require("../util/transformThemeValue")); | ||
const _index = /*#__PURE__*/ _interop_require_default(require("../value-parser/index")); | ||
const _normalizeScreens = require("../util/normalizeScreens"); | ||
const _buildMediaQuery = /*#__PURE__*/ _interop_require_default(require("../util/buildMediaQuery")); | ||
const _toPath = require("../util/toPath"); | ||
const _withAlphaVariable = require("../util/withAlphaVariable"); | ||
const _pluginUtils = require("../util/pluginUtils"); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("../util/log")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -23,6 +31,6 @@ default: obj | ||
function findClosestExistingPath(theme, path) { | ||
let parts = (0, _toPath).toPath(path); | ||
let parts = (0, _toPath.toPath)(path); | ||
do { | ||
parts.pop(); | ||
if ((0, _dlv).default(theme, parts) !== undefined) break; | ||
if ((0, _dlv.default)(theme, parts) !== undefined) break; | ||
}while (parts.length); | ||
@@ -39,4 +47,3 @@ return parts.length ? parts : undefined; | ||
function list(items) { | ||
return items.map((key)=>`'${key}'` | ||
).join(", "); | ||
return items.map((key)=>`'${key}'`).join(", "); | ||
} | ||
@@ -46,10 +53,10 @@ function listKeys(obj) { | ||
} | ||
function validatePath(config, path, defaultValue) { | ||
const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+/g, "").replace(/['"]+$/g, ""); | ||
const pathSegments = Array.isArray(path) ? path : (0, _toPath).toPath(pathString); | ||
const value = (0, _dlv).default(config.theme, pathSegments, defaultValue); | ||
function validatePath(config, path, defaultValue, themeOpts = {}) { | ||
const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+|['"]+$/g, ""); | ||
const pathSegments = Array.isArray(path) ? path : (0, _toPath.toPath)(pathString); | ||
const value = (0, _dlv.default)(config.theme, pathSegments, defaultValue); | ||
if (value === undefined) { | ||
let error = `'${pathString}' does not exist in your theme config.`; | ||
const parentSegments = pathSegments.slice(0, -1); | ||
const parentValue = (0, _dlv).default(config.theme, parentSegments); | ||
const parentValue = (0, _dlv.default)(config.theme, parentSegments); | ||
if (isObject(parentValue)) { | ||
@@ -59,5 +66,4 @@ const validKeys = Object.keys(parentValue).filter((key)=>validatePath(config, [ | ||
key | ||
]).isValid | ||
); | ||
const suggestion = (0, _didyoumean).default(pathSegments[pathSegments.length - 1], validKeys); | ||
]).isValid); | ||
const suggestion = (0, _didyoumean.default)(pathSegments[pathSegments.length - 1], validKeys); | ||
if (suggestion) { | ||
@@ -74,3 +80,3 @@ error += ` Did you mean '${pathToString([ | ||
if (closestPath) { | ||
const closestValue = (0, _dlv).default(config.theme, closestPath); | ||
const closestValue = (0, _dlv.default)(config.theme, closestPath); | ||
if (isObject(closestValue)) { | ||
@@ -96,4 +102,3 @@ error += ` '${pathToString(closestPath)}' has the following keys: ${listKeys(closestValue)}`; | ||
key | ||
]).isValid | ||
); | ||
]).isValid); | ||
if (validKeys.length) { | ||
@@ -114,16 +119,15 @@ error += ` Did you mean something like '${pathToString([ | ||
isValid: true, | ||
value: (0, _transformThemeValue).default(themeSection)(value) | ||
value: (0, _transformThemeValue.default)(themeSection)(value, themeOpts) | ||
}; | ||
} | ||
function extractArgs(node, vNodes, functions) { | ||
vNodes = vNodes.map((vNode)=>resolveVNode(node, vNode, functions) | ||
); | ||
vNodes = vNodes.map((vNode)=>resolveVNode(node, vNode, functions)); | ||
let args = [ | ||
"" | ||
]; | ||
for (let vNode1 of vNodes){ | ||
if (vNode1.type === "div" && vNode1.value === ",") { | ||
for (let vNode of vNodes){ | ||
if (vNode.type === "div" && vNode.value === ",") { | ||
args.push(""); | ||
} else { | ||
args[args.length - 1] += _postcssValueParser.default.stringify(vNode1); | ||
args[args.length - 1] += _index.default.stringify(vNode); | ||
} | ||
@@ -142,3 +146,5 @@ } | ||
function resolveFunctions(node, input, functions) { | ||
return (0, _postcssValueParser).default(input).walk((vNode)=>{ | ||
let hasAnyFn = Object.keys(functions).some((fn)=>input.includes(`${fn}(`)); | ||
if (!hasAnyFn) return input; | ||
return (0, _index.default)(input).walk((vNode)=>{ | ||
resolveVNode(node, vNode, functions); | ||
@@ -151,9 +157,71 @@ }).toString(); | ||
}; | ||
function _default({ tailwindConfig: config }) { | ||
/** | ||
* @param {string} path | ||
* @returns {Iterable<[path: string, alpha: string|undefined]>} | ||
*/ function* toPaths(path) { | ||
// Strip quotes from beginning and end of string | ||
// This allows the alpha value to be present inside of quotes | ||
path = path.replace(/^['"]+|['"]+$/g, ""); | ||
let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/); | ||
let alpha = undefined; | ||
yield [ | ||
path, | ||
undefined | ||
]; | ||
if (matches) { | ||
path = matches[1]; | ||
alpha = matches[2]; | ||
yield [ | ||
path, | ||
alpha | ||
]; | ||
} | ||
} | ||
/** | ||
* | ||
* @param {any} config | ||
* @param {string} path | ||
* @param {any} defaultValue | ||
*/ function resolvePath(config, path, defaultValue) { | ||
const results = Array.from(toPaths(path)).map(([path, alpha])=>{ | ||
return Object.assign(validatePath(config, path, defaultValue, { | ||
opacityValue: alpha | ||
}), { | ||
resolvedPath: path, | ||
alpha | ||
}); | ||
}); | ||
var _results_find; | ||
return (_results_find = results.find((result)=>result.isValid)) !== null && _results_find !== void 0 ? _results_find : results[0]; | ||
} | ||
function _default(context) { | ||
let config = context.tailwindConfig; | ||
let functions = { | ||
theme: (node, path, ...defaultValue)=>{ | ||
const { isValid , value , error } = validatePath(config, path, defaultValue.length ? defaultValue : undefined); | ||
let { isValid , value , error , alpha } = resolvePath(config, path, defaultValue.length ? defaultValue : undefined); | ||
if (!isValid) { | ||
var _parentNode_raws_tailwind; | ||
let parentNode = node.parent; | ||
let candidate = (_parentNode_raws_tailwind = parentNode === null || parentNode === void 0 ? void 0 : parentNode.raws.tailwind) === null || _parentNode_raws_tailwind === void 0 ? void 0 : _parentNode_raws_tailwind.candidate; | ||
if (parentNode && candidate !== undefined) { | ||
// Remove this utility from any caches | ||
context.markInvalidUtilityNode(parentNode); | ||
// Remove the CSS node from the markup | ||
parentNode.remove(); | ||
// Show a warning | ||
_log.default.warn("invalid-theme-key-in-class", [ | ||
`The utility \`${candidate}\` contains an invalid theme value and was not generated.` | ||
]); | ||
return; | ||
} | ||
throw node.error(error); | ||
} | ||
let maybeColor = (0, _pluginUtils.parseColorFormat)(value); | ||
let isColorFunction = maybeColor !== undefined && typeof maybeColor === "function"; | ||
if (alpha !== undefined || isColorFunction) { | ||
if (alpha === undefined) { | ||
alpha = 1.0; | ||
} | ||
value = (0, _withAlphaVariable.withAlphaValue)(maybeColor, alpha, maybeColor); | ||
} | ||
return value; | ||
@@ -163,9 +231,8 @@ }, | ||
screen = screen.replace(/^['"]+/g, "").replace(/['"]+$/g, ""); | ||
let screens = (0, _normalizeScreens).normalizeScreens(config.theme.screens); | ||
let screenDefinition = screens.find(({ name })=>name === screen | ||
); | ||
let screens = (0, _normalizeScreens.normalizeScreens)(config.theme.screens); | ||
let screenDefinition = screens.find(({ name })=>name === screen); | ||
if (!screenDefinition) { | ||
throw node.error(`The '${screen}' screen does not exist in your theme.`); | ||
} | ||
return (0, _buildMediaQuery).default(screenDefinition); | ||
return (0, _buildMediaQuery.default)(screenDefinition); | ||
} | ||
@@ -172,0 +239,0 @@ }; |
@@ -5,17 +5,15 @@ "use strict"; | ||
}); | ||
exports.default = expandApplyAtRules; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _generateRules = require("./generateRules"); | ||
var _bigSign = _interopRequireDefault(require("../util/bigSign")); | ||
var _escapeClassName = _interopRequireDefault(require("../util/escapeClassName")); | ||
function expandApplyAtRules(context) { | ||
return (root)=>{ | ||
// Build a cache of the user's CSS so we can use it to resolve classes used by @apply | ||
let localCache = lazyCache(()=>buildLocalApplyCache(root, context) | ||
); | ||
processApply(root, context, localCache); | ||
}; | ||
} | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return expandApplyAtRules; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
const _generateRules = require("./generateRules"); | ||
const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName")); | ||
const _applyImportantSelector = require("../util/applyImportantSelector"); | ||
const _pseudoElements = require("../util/pseudoElements"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -33,3 +31,3 @@ default: obj | ||
container.walkRules((rule)=>{ | ||
(0, _postcssSelectorParser).default((selectors)=>{ | ||
(0, _postcssselectorparser.default)((selectors)=>{ | ||
selectors.walkClasses((classSelector)=>{ | ||
@@ -45,16 +43,13 @@ let parentSelector = classSelector.parent.toString(); | ||
}); | ||
let normalizedGroups = Array.from(groups.values(), (classes)=>Array.from(classes) | ||
); | ||
let classes1 = normalizedGroups.flat(); | ||
return Object.assign(classes1, { | ||
let normalizedGroups = Array.from(groups.values(), (classes)=>Array.from(classes)); | ||
let classes = normalizedGroups.flat(); | ||
return Object.assign(classes, { | ||
groups: normalizedGroups | ||
}); | ||
} | ||
let selectorExtractor = (0, _postcssSelectorParser).default((root)=>root.nodes.map((node)=>node.toString() | ||
) | ||
); | ||
let selectorExtractor = (0, _postcssselectorparser.default)(); | ||
/** | ||
* @param {string} ruleSelectors | ||
*/ function extractSelectors(ruleSelectors) { | ||
return selectorExtractor.transformSync(ruleSelectors); | ||
return selectorExtractor.astSync(ruleSelectors); | ||
} | ||
@@ -69,4 +64,4 @@ function extractBaseCandidates(candidates, separator) { | ||
function prefix(context, selector) { | ||
let prefix1 = context.tailwindConfig.prefix; | ||
return typeof prefix1 === "function" ? prefix1(selector) : prefix1 + selector; | ||
let prefix = context.tailwindConfig.prefix; | ||
return typeof prefix === "function" ? prefix(selector) : prefix + selector; | ||
} | ||
@@ -152,8 +147,7 @@ function* pathToRoot(node) { | ||
/** @type {ApplyCache} */ let cache = new Map(); | ||
let highestOffset = context.layerOrder.user >> 4n; | ||
root.walkRules((rule, idx)=>{ | ||
root.walkRules((rule)=>{ | ||
// Ignore rules generated by Tailwind | ||
for (let node of pathToRoot(rule)){ | ||
var ref; | ||
if (((ref = node.raws.tailwind) === null || ref === void 0 ? void 0 : ref.layer) !== undefined) { | ||
var _node_raws_tailwind; | ||
if (((_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.layer) !== undefined) { | ||
return; | ||
@@ -164,2 +158,3 @@ } | ||
let container = nestedClone(rule); | ||
let sort = context.offsets.create("user"); | ||
for (let className of extractClasses(rule)){ | ||
@@ -171,6 +166,6 @@ let list = cache.get(className) || []; | ||
layer: "user", | ||
sort: BigInt(idx) + highestOffset, | ||
sort, | ||
important: false | ||
}, | ||
container, | ||
container | ||
]); | ||
@@ -192,7 +187,6 @@ } | ||
rule.clone() | ||
] | ||
)); | ||
])); | ||
continue; | ||
} | ||
let matches = Array.from((0, _generateRules).resolveMatches(candidate, context)); | ||
let matches = Array.from((0, _generateRules.resolveMatches)(candidate, context)); | ||
if (matches.length === 0) { | ||
@@ -232,7 +226,4 @@ context.notClassCache.add(candidate); | ||
return { | ||
get: (name)=>caches.flatMap((cache)=>cache.get(name) || [] | ||
) | ||
, | ||
has: (name)=>caches.some((cache)=>cache.has(name) | ||
) | ||
get: (name)=>caches.flatMap((cache)=>cache.get(name) || []), | ||
has: (name)=>caches.some((cache)=>cache.has(name)) | ||
}; | ||
@@ -292,26 +283,79 @@ } | ||
* Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover` | ||
*/ // TODO: Should we use postcss-selector-parser for this instead? | ||
function replaceSelector(selector, utilitySelectors, candidate) { | ||
let needle1 = `.${(0, _escapeClassName).default(candidate)}`; | ||
let needles = [ | ||
...new Set([ | ||
needle1, | ||
needle1.replace(/\\2c /g, "\\,") | ||
]) | ||
]; | ||
* | ||
* @param {string} selector | ||
* @param {string} utilitySelectors | ||
* @param {string} candidate | ||
*/ function replaceSelector(selector, utilitySelectors, candidate) { | ||
let selectorList = extractSelectors(selector); | ||
let utilitySelectorsList = extractSelectors(utilitySelectors); | ||
return extractSelectors(selector).map((s)=>{ | ||
let replaced = []; | ||
for (let utilitySelector of utilitySelectorsList){ | ||
let replacedSelector = utilitySelector; | ||
for (const needle of needles){ | ||
replacedSelector = replacedSelector.replace(needle, s); | ||
let candidateList = extractSelectors(`.${(0, _escapeClassName.default)(candidate)}`); | ||
let candidateClass = candidateList.nodes[0].nodes[0]; | ||
selectorList.each((sel)=>{ | ||
/** @type {Set<import('postcss-selector-parser').Selector>} */ let replaced = new Set(); | ||
utilitySelectorsList.each((utilitySelector)=>{ | ||
let hasReplaced = false; | ||
utilitySelector = utilitySelector.clone(); | ||
utilitySelector.walkClasses((node)=>{ | ||
if (node.value !== candidateClass.value) { | ||
return; | ||
} | ||
// Don't replace multiple instances of the same class | ||
// This is theoretically correct but only partially | ||
// We'd need to generate every possible permutation of the replacement | ||
// For example with `.foo + .foo { … }` and `section { @apply foo; }` | ||
// We'd need to generate all of these: | ||
// - `.foo + .foo` | ||
// - `.foo + section` | ||
// - `section + .foo` | ||
// - `section + section` | ||
if (hasReplaced) { | ||
return; | ||
} | ||
// Since you can only `@apply` class names this is sufficient | ||
// We want to replace the matched class name with the selector the user is using | ||
// Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)` | ||
node.replaceWith(...sel.nodes.map((node)=>node.clone())); | ||
// Record that we did something and we want to use this new selector | ||
replaced.add(utilitySelector); | ||
hasReplaced = true; | ||
}); | ||
}); | ||
// Sort tag names before class names (but only sort each group (separated by a combinator) | ||
// separately and not in total) | ||
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section` | ||
for (let sel of replaced){ | ||
let groups = [ | ||
[] | ||
]; | ||
for (let node of sel.nodes){ | ||
if (node.type === "combinator") { | ||
groups.push(node); | ||
groups.push([]); | ||
} else { | ||
let last = groups[groups.length - 1]; | ||
last.push(node); | ||
} | ||
} | ||
if (replacedSelector === utilitySelector) { | ||
continue; | ||
sel.nodes = []; | ||
for (let group of groups){ | ||
if (Array.isArray(group)) { | ||
group.sort((a, b)=>{ | ||
if (a.type === "tag" && b.type === "class") { | ||
return -1; | ||
} else if (a.type === "class" && b.type === "tag") { | ||
return 1; | ||
} else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) { | ||
return -1; | ||
} else if (a.type === "pseudo" && a.value.startsWith("::") && b.type === "class") { | ||
return 1; | ||
} | ||
return 0; | ||
}); | ||
} | ||
sel.nodes = sel.nodes.concat(group); | ||
} | ||
replaced.push(replacedSelector); | ||
} | ||
return replaced.join(", "); | ||
}).join(", "); | ||
sel.replaceWith(...replaced); | ||
}); | ||
return selectorList.toString(); | ||
} | ||
@@ -332,5 +376,4 @@ let perParentApplies = new Map(); | ||
if (apply.parent.name === "screen") { | ||
const screenType = apply.parent.params; | ||
throw apply.error(`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates.map((c)=>`${screenType}:${c}` | ||
).join(" ")} instead.`); | ||
let screenType = apply.parent.params; | ||
throw apply.error(`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates.map((c)=>`${screenType}:${c}`).join(" ")} instead.`); | ||
} | ||
@@ -351,2 +394,15 @@ throw apply.error(`@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`); | ||
let rules = applyClassCache.get(applyCandidate); | ||
// Verify that we can apply the class | ||
for (let [, rule] of rules){ | ||
if (rule.type === "atrule") { | ||
continue; | ||
} | ||
rule.walkRules(()=>{ | ||
throw apply.error([ | ||
`The \`${applyCandidate}\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.`, | ||
"Rewrite the selector without nesting or configure the `tailwindcss/nesting` plugin:", | ||
"https://tailwindcss.com/docs/using-with-preprocessors#nesting" | ||
].join("\n")); | ||
}); | ||
} | ||
candidates.push([ | ||
@@ -359,5 +415,5 @@ applyCandidate, | ||
} | ||
for (const [parent, [candidates1, atApplySource]] of perParentApplies){ | ||
for (let [parent, [candidates, atApplySource]] of perParentApplies){ | ||
let siblings = []; | ||
for (let [applyCandidate, important, rules] of candidates1){ | ||
for (let [applyCandidate, important, rules] of candidates){ | ||
let potentialApplyCandidates = [ | ||
@@ -367,12 +423,10 @@ applyCandidate, | ||
applyCandidate | ||
], context.tailwindConfig.separator), | ||
], context.tailwindConfig.separator) | ||
]; | ||
for (let [meta, node1] of rules){ | ||
for (let [meta, node] of rules){ | ||
let parentClasses = extractClasses(parent); | ||
let nodeClasses = extractClasses(node1); | ||
let nodeClasses = extractClasses(node); | ||
// When we encounter a rule like `.dark .a, .b { … }` we only want to be left with `[.dark, .a]` if the base applyCandidate is `.a` or with `[.b]` if the base applyCandidate is `.b` | ||
// So we've split them into groups | ||
nodeClasses = nodeClasses.groups.filter((classList)=>classList.some((className)=>potentialApplyCandidates.includes(className) | ||
) | ||
).flat(); | ||
nodeClasses = nodeClasses.groups.filter((classList)=>classList.some((className)=>potentialApplyCandidates.includes(className))).flat(); | ||
// Add base utility classes from the @apply node to the list of | ||
@@ -401,10 +455,9 @@ // classes to check whether it intersects and therefore results in a | ||
nodeClasses = nodeClasses.concat(extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)); | ||
let intersects = parentClasses.some((selector)=>nodeClasses.includes(selector) | ||
); | ||
let intersects = parentClasses.some((selector)=>nodeClasses.includes(selector)); | ||
if (intersects) { | ||
throw node1.error(`You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`); | ||
throw node.error(`You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`); | ||
} | ||
let root = _postcss.default.root({ | ||
nodes: [ | ||
node1.clone() | ||
node.clone() | ||
] | ||
@@ -416,3 +469,3 @@ }); | ||
}); | ||
let canRewriteSelector = node1.type !== "atrule" || node1.type === "atrule" && node1.name !== "keyframes"; | ||
let canRewriteSelector = node.type !== "atrule" || node.type === "atrule" && node.name !== "keyframes"; | ||
if (canRewriteSelector) { | ||
@@ -451,4 +504,3 @@ root.walkRules((rule)=>{ | ||
// case it would result in `{}` instead of `.something-unrelated {}` | ||
if (!extractClasses(rule).some((candidate)=>candidate === applyCandidate | ||
)) { | ||
if (!extractClasses(rule).some((candidate)=>candidate === applyCandidate)) { | ||
rule.remove(); | ||
@@ -463,6 +515,12 @@ return; | ||
let parentSelector = isGenerated && importantSelector && parent.selector.indexOf(importantSelector) === 0 ? parent.selector.slice(importantSelector.length) : parent.selector; | ||
// If the selector becomes empty after replacing the important selector | ||
// This means that it's the same as the parent selector and we don't want to replace it | ||
// Otherwise we'll crash | ||
if (parentSelector === "") { | ||
parentSelector = parent.selector; | ||
} | ||
rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate); | ||
// And then re-add it if it was removed | ||
if (importantSelector && parentSelector !== parent.selector) { | ||
rule.selector = `${importantSelector} ${rule.selector}`; | ||
rule.selector = (0, _applyImportantSelector.applyImportantSelector)(rule.selector, importantSelector); | ||
} | ||
@@ -472,12 +530,17 @@ rule.walkDecls((d)=>{ | ||
}); | ||
// Move pseudo elements to the end of the selector (if necessary) | ||
let selector = (0, _postcssselectorparser.default)().astSync(rule.selector); | ||
selector.each((sel)=>(0, _pseudoElements.movePseudos)(sel)); | ||
rule.selector = selector.toString(); | ||
}); | ||
} | ||
// It could be that the node we were inserted was removed because the class didn't match | ||
// If that was the *only* rule in the parent, then we have nothing add so we skip it | ||
if (!root.nodes[0]) { | ||
continue; | ||
} | ||
// Insert it | ||
siblings.push([ | ||
// Ensure that when we are sorting, that we take the layer order into account | ||
{ | ||
...meta, | ||
sort: meta.sort | context.layerOrder[meta.layer] | ||
}, | ||
root.nodes[0], | ||
meta.sort, | ||
root.nodes[0] | ||
]); | ||
@@ -487,15 +550,13 @@ } | ||
// Inject the rules, sorted, correctly | ||
let nodes = siblings.sort(([a], [z])=>(0, _bigSign).default(a.sort - z.sort) | ||
).map((s)=>s[1] | ||
); | ||
let nodes = context.offsets.sort(siblings).map((s)=>s[1]); | ||
// `parent` refers to the node at `.abc` in: .abc { @apply mt-2 } | ||
parent.after(nodes); | ||
} | ||
for (let apply1 of applies){ | ||
for (let apply of applies){ | ||
// If there are left-over declarations, just remove the @apply | ||
if (apply1.parent.nodes.length > 1) { | ||
apply1.remove(); | ||
if (apply.parent.nodes.length > 1) { | ||
apply.remove(); | ||
} else { | ||
// The node is empty, drop the full node | ||
apply1.parent.remove(); | ||
apply.parent.remove(); | ||
} | ||
@@ -506,1 +567,8 @@ } | ||
} | ||
function expandApplyAtRules(context) { | ||
return (root)=>{ | ||
// Build a cache of the user's CSS so we can use it to resolve classes used by @apply | ||
let localCache = lazyCache(()=>buildLocalApplyCache(root, context)); | ||
processApply(root, context, localCache); | ||
}; | ||
} |
@@ -5,12 +5,125 @@ "use strict"; | ||
}); | ||
exports.default = expandTailwindAtRules; | ||
var _quickLru = _interopRequireDefault(require("quick-lru")); | ||
var sharedState = _interopRequireWildcard(require("./sharedState")); | ||
var _generateRules = require("./generateRules"); | ||
var _bigSign = _interopRequireDefault(require("../util/bigSign")); | ||
var _log = _interopRequireDefault(require("../util/log")); | ||
var _cloneNodes = _interopRequireDefault(require("../util/cloneNodes")); | ||
var _defaultExtractor = require("./defaultExtractor"); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return expandTailwindAtRules; | ||
} | ||
}); | ||
const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); | ||
const _quicklru = /*#__PURE__*/ _interop_require_default(require("@alloc/quick-lru")); | ||
const _sharedState = /*#__PURE__*/ _interop_require_wildcard(require("./sharedState")); | ||
const _generateRules = require("./generateRules"); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("../util/log")); | ||
const _cloneNodes = /*#__PURE__*/ _interop_require_default(require("../util/cloneNodes")); | ||
const _defaultExtractor = require("./defaultExtractor"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interop_require_wildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
} | ||
let env = _sharedState.env; | ||
const builtInExtractors = { | ||
DEFAULT: _defaultExtractor.defaultExtractor | ||
}; | ||
const builtInTransformers = { | ||
DEFAULT: (content)=>content, | ||
svelte: (content)=>content.replace(/(?:^|\s)class:/g, " ") | ||
}; | ||
function getExtractor(context, fileExtension) { | ||
let extractors = context.tailwindConfig.content.extract; | ||
return extractors[fileExtension] || extractors.DEFAULT || builtInExtractors[fileExtension] || builtInExtractors.DEFAULT(context); | ||
} | ||
function getTransformer(tailwindConfig, fileExtension) { | ||
let transformers = tailwindConfig.content.transform; | ||
return transformers[fileExtension] || transformers.DEFAULT || builtInTransformers[fileExtension] || builtInTransformers.DEFAULT; | ||
} | ||
let extractorCache = new WeakMap(); | ||
// Scans template contents for possible classes. This is a hot path on initial build but | ||
// not too important for subsequent builds. The faster the better though — if we can speed | ||
// up these regexes by 50% that could cut initial build time by like 20%. | ||
function getClassCandidates(content, extractor, candidates, seen) { | ||
if (!extractorCache.has(extractor)) { | ||
extractorCache.set(extractor, new _quicklru.default({ | ||
maxSize: 25000 | ||
})); | ||
} | ||
for (let line of content.split("\n")){ | ||
line = line.trim(); | ||
if (seen.has(line)) { | ||
continue; | ||
} | ||
seen.add(line); | ||
if (extractorCache.get(extractor).has(line)) { | ||
for (let match of extractorCache.get(extractor).get(line)){ | ||
candidates.add(match); | ||
} | ||
} else { | ||
let extractorMatches = extractor(line).filter((s)=>s !== "!*"); | ||
let lineMatchesSet = new Set(extractorMatches); | ||
for (let match of lineMatchesSet){ | ||
candidates.add(match); | ||
} | ||
extractorCache.get(extractor).set(line, lineMatchesSet); | ||
} | ||
} | ||
} | ||
/** | ||
* | ||
* @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules | ||
* @param {*} context | ||
*/ function buildStylesheet(rules, context) { | ||
let sortedRules = context.offsets.sort(rules); | ||
let returnValue = { | ||
base: new Set(), | ||
defaults: new Set(), | ||
components: new Set(), | ||
utilities: new Set(), | ||
variants: new Set() | ||
}; | ||
for (let [sort, rule] of sortedRules){ | ||
returnValue[sort.layer].add(rule); | ||
} | ||
return returnValue; | ||
} | ||
function expandTailwindAtRules(context) { | ||
return (root)=>{ | ||
return async (root)=>{ | ||
let layerNodes = { | ||
@@ -33,18 +146,34 @@ base: null, | ||
}); | ||
if (Object.values(layerNodes).every((n)=>n === null | ||
)) { | ||
if (Object.values(layerNodes).every((n)=>n === null)) { | ||
return root; | ||
} | ||
var _context_candidates; | ||
// --- | ||
// Find potential rules in changed files | ||
let candidates = new Set([ | ||
sharedState.NOT_ON_DEMAND | ||
...(_context_candidates = context.candidates) !== null && _context_candidates !== void 0 ? _context_candidates : [], | ||
_sharedState.NOT_ON_DEMAND | ||
]); | ||
let seen = new Set(); | ||
env.DEBUG && console.time("Reading changed files"); | ||
for (let { content , extension } of context.changedContent){ | ||
let transformer = getTransformer(context.tailwindConfig, extension); | ||
let extractor = getExtractor(context, extension); | ||
getClassCandidates(transformer(content), extractor, candidates, seen); | ||
/** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */ let regexParserContent = []; | ||
for (let item of context.changedContent){ | ||
let transformer = getTransformer(context.tailwindConfig, item.extension); | ||
let extractor = getExtractor(context, item.extension); | ||
regexParserContent.push([ | ||
item, | ||
{ | ||
transformer, | ||
extractor | ||
} | ||
]); | ||
} | ||
const BATCH_SIZE = 500; | ||
for(let i = 0; i < regexParserContent.length; i += BATCH_SIZE){ | ||
let batch = regexParserContent.slice(i, i + BATCH_SIZE); | ||
await Promise.all(batch.map(async ([{ file , content }, { transformer , extractor }])=>{ | ||
content = file ? await _fs.default.promises.readFile(file, "utf8") : content; | ||
getClassCandidates(transformer(content), extractor, candidates, seen); | ||
})); | ||
} | ||
env.DEBUG && console.timeEnd("Reading changed files"); | ||
@@ -55,3 +184,12 @@ // --- | ||
env.DEBUG && console.time("Generate rules"); | ||
let rules = (0, _generateRules).generateRules(candidates, context); | ||
env.DEBUG && console.time("Sorting candidates"); | ||
let sortedCandidates = new Set([ | ||
...candidates | ||
].sort((a, z)=>{ | ||
if (a === z) return 0; | ||
if (a < z) return -1; | ||
return 1; | ||
})); | ||
env.DEBUG && console.timeEnd("Sorting candidates"); | ||
(0, _generateRules.generateRules)(sortedCandidates, context); | ||
env.DEBUG && console.timeEnd("Generate rules"); | ||
@@ -61,5 +199,2 @@ // We only ever add to the classCache, so if it didn't grow, there is nothing new. | ||
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) { | ||
for (let rule of rules){ | ||
context.ruleCache.add(rule); | ||
} | ||
context.stylesheetCache = buildStylesheet([ | ||
@@ -70,7 +205,7 @@ ...context.ruleCache | ||
env.DEBUG && console.timeEnd("Build stylesheet"); | ||
let { defaults: defaultNodes , base: baseNodes , components: componentNodes , utilities: utilityNodes , variants: screenNodes , } = context.stylesheetCache; | ||
let { defaults: defaultNodes , base: baseNodes , components: componentNodes , utilities: utilityNodes , variants: screenNodes } = context.stylesheetCache; | ||
// --- | ||
// Replace any Tailwind directives with generated CSS | ||
if (layerNodes.base) { | ||
layerNodes.base.before((0, _cloneNodes).default([ | ||
layerNodes.base.before((0, _cloneNodes.default)([ | ||
...baseNodes, | ||
@@ -84,3 +219,3 @@ ...defaultNodes | ||
if (layerNodes.components) { | ||
layerNodes.components.before((0, _cloneNodes).default([ | ||
layerNodes.components.before((0, _cloneNodes.default)([ | ||
...componentNodes | ||
@@ -93,3 +228,3 @@ ], layerNodes.components.source, { | ||
if (layerNodes.utilities) { | ||
layerNodes.utilities.before((0, _cloneNodes).default([ | ||
layerNodes.utilities.before((0, _cloneNodes.default)([ | ||
...utilityNodes | ||
@@ -103,4 +238,4 @@ ], layerNodes.utilities.source, { | ||
const variantNodes = Array.from(screenNodes).filter((node)=>{ | ||
var ref; | ||
const parentLayer = (ref = node.raws.tailwind) === null || ref === void 0 ? void 0 : ref.parentLayer; | ||
var _node_raws_tailwind; | ||
const parentLayer = (_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.parentLayer; | ||
if (parentLayer === "components") { | ||
@@ -115,3 +250,3 @@ return layerNodes.components !== null; | ||
if (layerNodes.variants) { | ||
layerNodes.variants.before((0, _cloneNodes).default(variantNodes, layerNodes.variants.source, { | ||
layerNodes.variants.before((0, _cloneNodes.default)(variantNodes, layerNodes.variants.source, { | ||
layer: "variants" | ||
@@ -121,10 +256,13 @@ })); | ||
} else if (variantNodes.length > 0) { | ||
root.append((0, _cloneNodes).default(variantNodes, root.source, { | ||
root.append((0, _cloneNodes.default)(variantNodes, root.source, { | ||
layer: "variants" | ||
})); | ||
} | ||
var _root_source_end; | ||
// TODO: Why is the root node having no source location for `end` possible? | ||
root.source.end = (_root_source_end = root.source.end) !== null && _root_source_end !== void 0 ? _root_source_end : root.source.start; | ||
// If we've got a utility layer and no utilities are generated there's likely something wrong | ||
const hasUtilityVariants = variantNodes.some((node)=>{ | ||
var ref; | ||
return ((ref = node.raws.tailwind) === null || ref === void 0 ? void 0 : ref.parentLayer) === "utilities"; | ||
var _node_raws_tailwind; | ||
return ((_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.parentLayer) === "utilities"; | ||
}); | ||
@@ -134,3 +272,3 @@ if (layerNodes.utilities && utilityNodes.size === 0 && !hasUtilityVariants) { | ||
"No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.", | ||
"https://tailwindcss.com/docs/content-configuration", | ||
"https://tailwindcss.com/docs/content-configuration" | ||
]); | ||
@@ -141,3 +279,3 @@ } | ||
console.log("Potential classes: ", candidates.size); | ||
console.log("Active contexts: ", sharedState.contextSourcesMap.size); | ||
console.log("Active contexts: ", _sharedState.contextSourcesMap.size); | ||
} | ||
@@ -154,122 +292,1 @@ // Clear the cache for the changed files | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
} | ||
} | ||
let env = sharedState.env; | ||
const builtInExtractors = { | ||
DEFAULT: _defaultExtractor.defaultExtractor | ||
}; | ||
const builtInTransformers = { | ||
DEFAULT: (content)=>content | ||
, | ||
svelte: (content)=>content.replace(/(?:^|\s)class:/g, " ") | ||
}; | ||
function getExtractor(context, fileExtension) { | ||
let extractors = context.tailwindConfig.content.extract; | ||
return extractors[fileExtension] || extractors.DEFAULT || builtInExtractors[fileExtension] || builtInExtractors.DEFAULT(context); | ||
} | ||
function getTransformer(tailwindConfig, fileExtension) { | ||
let transformers = tailwindConfig.content.transform; | ||
return transformers[fileExtension] || transformers.DEFAULT || builtInTransformers[fileExtension] || builtInTransformers.DEFAULT; | ||
} | ||
let extractorCache = new WeakMap(); | ||
// Scans template contents for possible classes. This is a hot path on initial build but | ||
// not too important for subsequent builds. The faster the better though — if we can speed | ||
// up these regexes by 50% that could cut initial build time by like 20%. | ||
function getClassCandidates(content, extractor, candidates, seen) { | ||
if (!extractorCache.has(extractor)) { | ||
extractorCache.set(extractor, new _quickLru.default({ | ||
maxSize: 25000 | ||
})); | ||
} | ||
for (let line of content.split("\n")){ | ||
line = line.trim(); | ||
if (seen.has(line)) { | ||
continue; | ||
} | ||
seen.add(line); | ||
if (extractorCache.get(extractor).has(line)) { | ||
for (let match of extractorCache.get(extractor).get(line)){ | ||
candidates.add(match); | ||
} | ||
} else { | ||
let extractorMatches = extractor(line).filter((s)=>s !== "!*" | ||
); | ||
let lineMatchesSet = new Set(extractorMatches); | ||
for (let match of lineMatchesSet){ | ||
candidates.add(match); | ||
} | ||
extractorCache.get(extractor).set(line, lineMatchesSet); | ||
} | ||
} | ||
} | ||
function buildStylesheet(rules, context) { | ||
let sortedRules = rules.sort(([a], [z])=>(0, _bigSign).default(a - z) | ||
); | ||
let returnValue = { | ||
base: new Set(), | ||
defaults: new Set(), | ||
components: new Set(), | ||
utilities: new Set(), | ||
variants: new Set(), | ||
// All the CSS that is not Tailwind related can be put in this bucket. This | ||
// will make it easier to later use this information when we want to | ||
// `@apply` for example. The main reason we do this here is because we | ||
// still need to make sure the order is correct. Last but not least, we | ||
// will make sure to always re-inject this section into the css, even if | ||
// certain rules were not used. This means that it will look like a no-op | ||
// from the user's perspective, but we gathered all the useful information | ||
// we need. | ||
user: new Set() | ||
}; | ||
for (let [sort, rule] of sortedRules){ | ||
if (sort >= context.minimumScreen) { | ||
returnValue.variants.add(rule); | ||
continue; | ||
} | ||
if (sort & context.layerOrder.base) { | ||
returnValue.base.add(rule); | ||
continue; | ||
} | ||
if (sort & context.layerOrder.defaults) { | ||
returnValue.defaults.add(rule); | ||
continue; | ||
} | ||
if (sort & context.layerOrder.components) { | ||
returnValue.components.add(rule); | ||
continue; | ||
} | ||
if (sort & context.layerOrder.utilities) { | ||
returnValue.utilities.add(rule); | ||
continue; | ||
} | ||
if (sort & context.layerOrder.user) { | ||
returnValue.user.add(rule); | ||
continue; | ||
} | ||
} | ||
return returnValue; | ||
} |
@@ -5,18 +5,36 @@ "use strict"; | ||
}); | ||
exports.generateRules = exports.resolveMatches = void 0; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _parseObjectStyles = _interopRequireDefault(require("../util/parseObjectStyles")); | ||
var _isPlainObject = _interopRequireDefault(require("../util/isPlainObject")); | ||
var _prefixSelector = _interopRequireDefault(require("../util/prefixSelector")); | ||
var _pluginUtils = require("../util/pluginUtils"); | ||
var _log = _interopRequireDefault(require("../util/log")); | ||
var sharedState = _interopRequireWildcard(require("./sharedState")); | ||
var _formatVariantSelector = require("../util/formatVariantSelector"); | ||
var _nameClass = require("../util/nameClass"); | ||
var _dataTypes = require("../util/dataTypes"); | ||
var _setupContextUtils = require("./setupContextUtils"); | ||
var _isValidArbitraryValue = _interopRequireDefault(require("../util/isValidArbitraryValue")); | ||
var _splitAtTopLevelOnlyJs = require("../util/splitAtTopLevelOnly.js"); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
getClassNameFromSelector: function() { | ||
return getClassNameFromSelector; | ||
}, | ||
resolveMatches: function() { | ||
return resolveMatches; | ||
}, | ||
generateRules: function() { | ||
return generateRules; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
const _parseObjectStyles = /*#__PURE__*/ _interop_require_default(require("../util/parseObjectStyles")); | ||
const _isPlainObject = /*#__PURE__*/ _interop_require_default(require("../util/isPlainObject")); | ||
const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector")); | ||
const _pluginUtils = require("../util/pluginUtils"); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("../util/log")); | ||
const _sharedState = /*#__PURE__*/ _interop_require_wildcard(require("./sharedState")); | ||
const _formatVariantSelector = require("../util/formatVariantSelector"); | ||
const _nameClass = require("../util/nameClass"); | ||
const _dataTypes = require("../util/dataTypes"); | ||
const _setupContextUtils = require("./setupContextUtils"); | ||
const _isSyntacticallyValidPropertyValue = /*#__PURE__*/ _interop_require_default(require("../util/isSyntacticallyValidPropertyValue")); | ||
const _splitAtTopLevelOnly = require("../util/splitAtTopLevelOnly.js"); | ||
const _featureFlags = require("../featureFlags"); | ||
const _applyImportantSelector = require("../util/applyImportantSelector"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -26,26 +44,43 @@ default: obj | ||
} | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interop_require_wildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
} | ||
let classNameParser = (0, _postcssSelectorParser).default((selectors)=>{ | ||
return selectors.first.filter(({ type })=>type === "class" | ||
).pop().value; | ||
let classNameParser = (0, _postcssselectorparser.default)((selectors)=>{ | ||
return selectors.first.filter(({ type })=>type === "class").pop().value; | ||
}); | ||
@@ -66,2 +101,3 @@ function getClassNameFromSelector(selector) { | ||
let dashIdx; | ||
let wasSlash = false; | ||
if (lastIndex === Infinity && candidate.endsWith("]")) { | ||
@@ -71,6 +107,13 @@ let bracketIdx = candidate.indexOf("["); | ||
// eg. string[] | ||
dashIdx = [ | ||
"-", | ||
"/" | ||
].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1; | ||
if (candidate[bracketIdx - 1] === "-") { | ||
dashIdx = bracketIdx - 1; | ||
} else if (candidate[bracketIdx - 1] === "/") { | ||
dashIdx = bracketIdx - 1; | ||
wasSlash = true; | ||
} else { | ||
dashIdx = -1; | ||
} | ||
} else if (lastIndex === Infinity && candidate.includes("/")) { | ||
dashIdx = candidate.lastIndexOf("/"); | ||
wasSlash = true; | ||
} else { | ||
@@ -83,3 +126,8 @@ dashIdx = candidate.lastIndexOf("-", lastIndex); | ||
let prefix = candidate.slice(0, dashIdx); | ||
let modifier = candidate.slice(dashIdx + 1); | ||
let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1); | ||
lastIndex = dashIdx - 1; | ||
// TODO: This feels a bit hacky | ||
if (prefix === "" || modifier === "/") { | ||
continue; | ||
} | ||
yield [ | ||
@@ -89,3 +137,2 @@ prefix, | ||
]; | ||
lastIndex = dashIdx - 1; | ||
} | ||
@@ -112,3 +159,3 @@ } | ||
let shouldPrependNegative = classCandidate.startsWith("-"); | ||
r.selector = (0, _prefixSelector).default(context.tailwindConfig.prefix, r.selector, shouldPrependNegative); | ||
r.selector = (0, _prefixSelector.default)(context.tailwindConfig.prefix, r.selector, shouldPrependNegative); | ||
}); | ||
@@ -125,2 +172,5 @@ match[1] = container.nodes[0]; | ||
let result = []; | ||
function isInKeyframes(rule) { | ||
return rule.parent && rule.parent.type === "atrule" && rule.parent.name === "keyframes"; | ||
} | ||
for (let [meta, rule] of matches){ | ||
@@ -133,10 +183,14 @@ let container = _postcss.default.root({ | ||
container.walkRules((r)=>{ | ||
r.selector = (0, _pluginUtils).updateAllClasses(r.selector, (className)=>{ | ||
if (className === classCandidate) { | ||
return `!${className}`; | ||
} | ||
return className; | ||
}); | ||
r.walkDecls((d)=>d.important = true | ||
); | ||
// Declarations inside keyframes cannot be marked as important | ||
// They will be ignored by the browser | ||
if (isInKeyframes(r)) { | ||
return; | ||
} | ||
let ast = (0, _postcssselectorparser.default)().astSync(r.selector); | ||
// Remove extraneous selectors that do not include the base candidate | ||
ast.each((sel)=>(0, _formatVariantSelector.eliminateIrrelevantSelectors)(sel, classCandidate)); | ||
// Update all instances of the base candidate to include the important marker | ||
(0, _pluginUtils.updateAllClasses)(ast, (className)=>className === classCandidate ? `!${className}` : className); | ||
r.selector = ast.toString(); | ||
r.walkDecls((d)=>d.important = true); | ||
}); | ||
@@ -165,28 +219,74 @@ result.push([ | ||
} | ||
let args; | ||
// Find partial arbitrary variants | ||
/** @type {{modifier: string | null, value: string | null}} */ let args = { | ||
modifier: null, | ||
value: _sharedState.NONE | ||
}; | ||
// Retrieve "modifier" | ||
{ | ||
let [baseVariant, ...modifiers] = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(variant, "/"); | ||
// This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500` | ||
// In this case 1/10 is a value but /20 is a modifier | ||
if (modifiers.length > 1) { | ||
baseVariant = baseVariant + "/" + modifiers.slice(0, -1).join("/"); | ||
modifiers = modifiers.slice(-1); | ||
} | ||
if (modifiers.length && !context.variantMap.has(variant)) { | ||
variant = baseVariant; | ||
args.modifier = modifiers[0]; | ||
if (!(0, _featureFlags.flagEnabled)(context.tailwindConfig, "generalizedModifiers")) { | ||
return []; | ||
} | ||
} | ||
} | ||
// Retrieve "arbitrary value" | ||
if (variant.endsWith("]") && !variant.startsWith("[")) { | ||
args = variant.slice(variant.lastIndexOf("[") + 1, -1); | ||
variant = variant.slice(0, variant.indexOf(args) - 1 /* - */ - 1 /* [ */ ); | ||
// We either have: | ||
// @[200px] | ||
// group-[:hover] | ||
// | ||
// But we don't want: | ||
// @-[200px] (`-` is incorrect) | ||
// group[:hover] (`-` is missing) | ||
let match = /(.)(-?)\[(.*)\]/g.exec(variant); | ||
if (match) { | ||
let [, char, separator, value] = match; | ||
// @-[200px] case | ||
if (char === "@" && separator === "-") return []; | ||
// group[:hover] case | ||
if (char !== "@" && separator === "") return []; | ||
variant = variant.replace(`${separator}[${value}]`, ""); | ||
args.value = value; | ||
} | ||
} | ||
// Register arbitrary variants | ||
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) { | ||
let selector = (0, _dataTypes).normalize(variant.slice(1, -1)); | ||
if (!(0, _setupContextUtils).isValidVariantFormatString(selector)) { | ||
let sort = context.offsets.recordVariant(variant); | ||
let selector = (0, _dataTypes.normalize)(variant.slice(1, -1)); | ||
let selectors = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(selector, ","); | ||
// We do not support multiple selectors for arbitrary variants | ||
if (selectors.length > 1) { | ||
return []; | ||
} | ||
let fn = (0, _setupContextUtils).parseVariant(selector); | ||
let sort = Array.from(context.variantOrder.values()).pop() << 1n; | ||
context.variantMap.set(variant, [ | ||
[ | ||
sort, | ||
fn | ||
] | ||
]); | ||
context.variantOrder.set(variant, sort); | ||
if (!selectors.every(_setupContextUtils.isValidVariantFormatString)) { | ||
return []; | ||
} | ||
let records = selectors.map((sel, idx)=>[ | ||
context.offsets.applyParallelOffset(sort, idx), | ||
(0, _setupContextUtils.parseVariant)(sel.trim()) | ||
]); | ||
context.variantMap.set(variant, records); | ||
} | ||
if (context.variantMap.has(variant)) { | ||
let variantFunctionTuples = context.variantMap.get(variant); | ||
var _context_variantOptions_get; | ||
let isArbitraryVariant = isArbitraryValue(variant); | ||
var _context_variantOptions_get_INTERNAL_FEATURES; | ||
let internalFeatures = (_context_variantOptions_get_INTERNAL_FEATURES = (_context_variantOptions_get = context.variantOptions.get(variant)) === null || _context_variantOptions_get === void 0 ? void 0 : _context_variantOptions_get[_setupContextUtils.INTERNAL_FEATURES]) !== null && _context_variantOptions_get_INTERNAL_FEATURES !== void 0 ? _context_variantOptions_get_INTERNAL_FEATURES : {}; | ||
let variantFunctionTuples = context.variantMap.get(variant).slice(); | ||
let result = []; | ||
for (let [meta, rule1] of matches){ | ||
let respectPrefix = (()=>{ | ||
if (isArbitraryVariant) return false; | ||
if (internalFeatures.respectPrefix === false) return false; | ||
return true; | ||
})(); | ||
for (let [meta, rule] of matches){ | ||
// Don't generate variants for user css | ||
@@ -198,13 +298,15 @@ if (meta.layer === "user") { | ||
nodes: [ | ||
rule1.clone() | ||
rule.clone() | ||
] | ||
}); | ||
for (let [variantSort, variantFunction] of variantFunctionTuples){ | ||
let clone = container.clone(); | ||
for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples){ | ||
let clone = (containerFromArray !== null && containerFromArray !== void 0 ? containerFromArray : container).clone(); | ||
let collectedFormats = []; | ||
let originals = new Map(); | ||
function prepareBackup() { | ||
if (originals.size > 0) return; // Already prepared, chicken out | ||
clone.walkRules((rule)=>originals.set(rule, rule.selector) | ||
); | ||
// Already prepared, chicken out | ||
if (clone.raws.neededBackup) { | ||
return; | ||
} | ||
clone.raws.neededBackup = true; | ||
clone.walkRules((rule)=>rule.raws.originalSelector = rule.selector); | ||
} | ||
@@ -244,8 +346,32 @@ function modifySelectors(modifierFunction) { | ||
format (selectorFormat) { | ||
collectedFormats.push(selectorFormat); | ||
collectedFormats.push({ | ||
format: selectorFormat, | ||
respectPrefix | ||
}); | ||
}, | ||
args | ||
}); | ||
// It can happen that a list of format strings is returned from within the function. In that | ||
// case, we have to process them as well. We can use the existing `variantSort`. | ||
if (Array.isArray(ruleWithVariant)) { | ||
for (let [idx, variantFunction] of ruleWithVariant.entries()){ | ||
// This is a little bit scary since we are pushing to an array of items that we are | ||
// currently looping over. However, you can also think of it like a processing queue | ||
// where you keep handling jobs until everything is done and each job can queue more | ||
// jobs if needed. | ||
variantFunctionTuples.push([ | ||
context.offsets.applyParallelOffset(variantSort, idx), | ||
variantFunction, | ||
// If the clone has been modified we have to pass that back | ||
// though so each rule can use the modified container | ||
clone.clone() | ||
]); | ||
} | ||
continue; | ||
} | ||
if (typeof ruleWithVariant === "string") { | ||
collectedFormats.push(ruleWithVariant); | ||
collectedFormats.push({ | ||
format: ruleWithVariant, | ||
respectPrefix | ||
}); | ||
} | ||
@@ -255,9 +381,11 @@ if (ruleWithVariant === null) { | ||
} | ||
// We filled the `originals`, therefore we assume that somebody touched | ||
// We had to backup selectors, therefore we assume that somebody touched | ||
// `container` or `modifySelectors`. Let's see if they did, so that we | ||
// can restore the selectors, and collect the format strings. | ||
if (originals.size > 0) { | ||
if (clone.raws.neededBackup) { | ||
delete clone.raws.neededBackup; | ||
clone.walkRules((rule)=>{ | ||
if (!originals.has(rule)) return; | ||
let before = originals.get(rule); | ||
let before = rule.raws.originalSelector; | ||
if (!before) return; | ||
delete rule.raws.originalSelector; | ||
if (before === rule.selector) return; // No mutation happened | ||
@@ -269,3 +397,3 @@ let modified = rule.selector; | ||
// classes, pseudos, ids, ... | ||
let rebuiltBase = (0, _postcssSelectorParser).default((selectors)=>{ | ||
let rebuiltBase = (0, _postcssselectorparser.default)((selectors)=>{ | ||
selectors.walkClasses((classNode)=>{ | ||
@@ -286,3 +414,6 @@ classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`; | ||
// format: .foo & | ||
collectedFormats.push(modified.replace(rebuiltBase, "&")); | ||
collectedFormats.push({ | ||
format: modified.replace(rebuiltBase, "&"), | ||
respectPrefix | ||
}); | ||
rule.selector = before; | ||
@@ -299,10 +430,10 @@ }); | ||
}; | ||
var _collectedFormats; | ||
var _meta_collectedFormats; | ||
let withOffset = [ | ||
{ | ||
...meta, | ||
sort: variantSort | meta.sort, | ||
collectedFormats: ((_collectedFormats = meta.collectedFormats) !== null && _collectedFormats !== void 0 ? _collectedFormats : []).concat(collectedFormats) | ||
sort: context.offsets.applyVariantOffset(meta.sort, variantSort, Object.assign(args, context.variantOptions.get(variant))), | ||
collectedFormats: ((_meta_collectedFormats = meta.collectedFormats) !== null && _meta_collectedFormats !== void 0 ? _meta_collectedFormats : []).concat(collectedFormats) | ||
}, | ||
clone.nodes[0], | ||
clone.nodes[0] | ||
]; | ||
@@ -318,3 +449,3 @@ result.push(withOffset); | ||
// PostCSS node | ||
if (!(0, _isPlainObject).default(rule) && !Array.isArray(rule)) { | ||
if (!(0, _isPlainObject.default)(rule) && !Array.isArray(rule)) { | ||
return [ | ||
@@ -333,3 +464,3 @@ [ | ||
if (!cache.has(rule)) { | ||
cache.set(rule, (0, _parseObjectStyles).default(rule)); | ||
cache.set(rule, (0, _parseObjectStyles.default)(rule)); | ||
} | ||
@@ -365,3 +496,3 @@ return [ | ||
node.walkDecls((decl)=>{ | ||
if (!isParsableCssValue(decl.name, decl.value)) { | ||
if (!isParsableCssValue(decl.prop, decl.value)) { | ||
isParsable = false; | ||
@@ -388,4 +519,4 @@ return false; | ||
function extractArbitraryProperty(classCandidate, context) { | ||
var ref; | ||
let [, property, value] = (ref = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/)) !== null && ref !== void 0 ? ref : []; | ||
var _classCandidate_match; | ||
let [, property, value] = (_classCandidate_match = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/)) !== null && _classCandidate_match !== void 0 ? _classCandidate_match : []; | ||
if (value === undefined) { | ||
@@ -397,22 +528,27 @@ return null; | ||
} | ||
if (!(0, _isValidArbitraryValue).default(value)) { | ||
if (!(0, _isSyntacticallyValidPropertyValue.default)(value)) { | ||
return null; | ||
} | ||
let normalized = (0, _dataTypes).normalize(value); | ||
let normalized = (0, _dataTypes.normalize)(value, { | ||
property | ||
}); | ||
if (!isParsableCssValue(property, normalized)) { | ||
return null; | ||
} | ||
let sort = context.offsets.arbitraryProperty(classCandidate); | ||
return [ | ||
[ | ||
{ | ||
sort: context.arbitraryPropertiesSort, | ||
layer: "utilities" | ||
sort, | ||
layer: "utilities", | ||
options: { | ||
respectImportant: true | ||
} | ||
}, | ||
()=>({ | ||
[(0, _nameClass).asClass(classCandidate)]: { | ||
[(0, _nameClass.asClass)(classCandidate)]: { | ||
[property]: normalized | ||
} | ||
}) | ||
, | ||
], | ||
] | ||
]; | ||
@@ -460,14 +596,17 @@ } | ||
function splitWithSeparator(input, separator) { | ||
if (input === sharedState.NOT_ON_DEMAND) { | ||
if (input === _sharedState.NOT_ON_DEMAND) { | ||
return [ | ||
sharedState.NOT_ON_DEMAND | ||
_sharedState.NOT_ON_DEMAND | ||
]; | ||
} | ||
return Array.from((0, _splitAtTopLevelOnlyJs).splitAtTopLevelOnly(input, separator)); | ||
return (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(input, separator); | ||
} | ||
function* recordCandidates(matches, classCandidate) { | ||
for (const match of matches){ | ||
var _match__options; | ||
var _match__options_preserveSource; | ||
match[1].raws.tailwind = { | ||
...match[1].raws.tailwind, | ||
classCandidate | ||
classCandidate, | ||
preserveSource: (_match__options_preserveSource = (_match__options = match[0].options) === null || _match__options === void 0 ? void 0 : _match__options.preserveSource) !== null && _match__options_preserveSource !== void 0 ? _match__options_preserveSource : false | ||
}; | ||
@@ -538,4 +677,8 @@ yield match; | ||
if (matchesPerPlugin.length > 0) { | ||
var ref; | ||
typesByMatches.set(matchesPerPlugin, (ref = sort.options) === null || ref === void 0 ? void 0 : ref.type); | ||
var _sort_options; | ||
var _sort_options_types, _sort_options1; | ||
let matchingTypes = Array.from((0, _pluginUtils.getMatchingTypes)((_sort_options_types = (_sort_options = sort.options) === null || _sort_options === void 0 ? void 0 : _sort_options.types) !== null && _sort_options_types !== void 0 ? _sort_options_types : [], modifier, (_sort_options1 = sort.options) !== null && _sort_options1 !== void 0 ? _sort_options1 : {}, context.tailwindConfig)).map(([_, type])=>type); | ||
if (matchingTypes.length > 0) { | ||
typesByMatches.set(matchesPerPlugin, matchingTypes); | ||
} | ||
matches.push(matchesPerPlugin); | ||
@@ -545,48 +688,80 @@ } | ||
if (isArbitraryValue(modifier)) { | ||
// When generated arbitrary values are ambiguous, we can't know | ||
// which to pick so don't generate any utilities for them | ||
if (matches.length > 1) { | ||
var ref1; | ||
let typesPerPlugin = matches.map((match)=>new Set([ | ||
...(ref1 = typesByMatches.get(match)) !== null && ref1 !== void 0 ? ref1 : [] | ||
]) | ||
); | ||
// Remove duplicates, so that we can detect proper unique types for each plugin. | ||
for (let pluginTypes of typesPerPlugin){ | ||
for (let type of pluginTypes){ | ||
let removeFromOwnGroup = false; | ||
for (let otherGroup of typesPerPlugin){ | ||
if (pluginTypes === otherGroup) continue; | ||
if (otherGroup.has(type)) { | ||
otherGroup.delete(type); | ||
removeFromOwnGroup = true; | ||
// Partition plugins in 2 categories so that we can start searching in the plugins that | ||
// don't have `any` as a type first. | ||
let [withAny, withoutAny] = matches.reduce((group, plugin)=>{ | ||
let hasAnyType = plugin.some(([{ options }])=>options.types.some(({ type })=>type === "any")); | ||
if (hasAnyType) { | ||
group[0].push(plugin); | ||
} else { | ||
group[1].push(plugin); | ||
} | ||
return group; | ||
}, [ | ||
[], | ||
[] | ||
]); | ||
function findFallback(matches) { | ||
// If only a single plugin matches, let's take that one | ||
if (matches.length === 1) { | ||
return matches[0]; | ||
} | ||
// Otherwise, find the plugin that creates a valid rule given the arbitrary value, and | ||
// also has the correct type which preferOnConflicts the plugin in case of clashes. | ||
return matches.find((rules)=>{ | ||
let matchingTypes = typesByMatches.get(rules); | ||
return rules.some(([{ options }, rule])=>{ | ||
if (!isParsableNode(rule)) { | ||
return false; | ||
} | ||
return options.types.some(({ type , preferOnConflict })=>matchingTypes.includes(type) && preferOnConflict); | ||
}); | ||
}); | ||
} | ||
var _findFallback; | ||
// Try to find a fallback plugin, because we already know that multiple plugins matched for | ||
// the given arbitrary value. | ||
let fallback = (_findFallback = findFallback(withoutAny)) !== null && _findFallback !== void 0 ? _findFallback : findFallback(withAny); | ||
if (fallback) { | ||
matches = [ | ||
fallback | ||
]; | ||
} else { | ||
var _typesByMatches_get; | ||
let typesPerPlugin = matches.map((match)=>new Set([ | ||
...(_typesByMatches_get = typesByMatches.get(match)) !== null && _typesByMatches_get !== void 0 ? _typesByMatches_get : [] | ||
])); | ||
// Remove duplicates, so that we can detect proper unique types for each plugin. | ||
for (let pluginTypes of typesPerPlugin){ | ||
for (let type of pluginTypes){ | ||
let removeFromOwnGroup = false; | ||
for (let otherGroup of typesPerPlugin){ | ||
if (pluginTypes === otherGroup) continue; | ||
if (otherGroup.has(type)) { | ||
otherGroup.delete(type); | ||
removeFromOwnGroup = true; | ||
} | ||
} | ||
if (removeFromOwnGroup) pluginTypes.delete(type); | ||
} | ||
if (removeFromOwnGroup) pluginTypes.delete(type); | ||
} | ||
} | ||
let messages = []; | ||
for (let [idx, group] of typesPerPlugin.entries()){ | ||
for (let type of group){ | ||
let rules = matches[idx].map(([, rule])=>rule | ||
).flat().map((rule)=>rule.toString().split("\n").slice(1, -1) // Remove selector and closing '}' | ||
.map((line)=>line.trim() | ||
).map((x)=>` ${x}` | ||
) // Re-indent | ||
.join("\n") | ||
).join("\n\n"); | ||
messages.push(` Use \`${candidate.replace("[", `[${type}:`)}\` for \`${rules.trim()}\``); | ||
break; | ||
let messages = []; | ||
for (let [idx, group] of typesPerPlugin.entries()){ | ||
for (let type of group){ | ||
let rules = matches[idx].map(([, rule])=>rule).flat().map((rule)=>rule.toString().split("\n").slice(1, -1) // Remove selector and closing '}' | ||
.map((line)=>line.trim()).map((x)=>` ${x}`) // Re-indent | ||
.join("\n")).join("\n\n"); | ||
messages.push(` Use \`${candidate.replace("[", `[${type}:`)}\` for \`${rules.trim()}\``); | ||
break; | ||
} | ||
} | ||
_log.default.warn([ | ||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`, | ||
...messages, | ||
`If this is content and not a class, replace it with \`${candidate.replace("[", "[").replace("]", "]")}\` to silence this warning.` | ||
]); | ||
continue; | ||
} | ||
_log.default.warn([ | ||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`, | ||
...messages, | ||
`If this is content and not a class, replace it with \`${candidate.replace("[", "[").replace("]", "]")}\` to silence this warning.`, | ||
]); | ||
continue; | ||
} | ||
matches = matches.map((list)=>list.filter((match)=>isParsableNode(match[1]) | ||
) | ||
); | ||
matches = matches.map((list)=>list.filter((match)=>isParsableNode(match[1]))); | ||
} | ||
@@ -602,35 +777,113 @@ matches = matches.flat(); | ||
} | ||
for (let match1 of matches){ | ||
match1[1].raws.tailwind = { | ||
...match1[1].raws.tailwind, | ||
for (let match of matches){ | ||
match[1].raws.tailwind = { | ||
...match[1].raws.tailwind, | ||
candidate | ||
}; | ||
// Apply final format selector | ||
if (match1[0].collectedFormats) { | ||
let finalFormat = (0, _formatVariantSelector).formatVariantSelector("&", ...match1[0].collectedFormats); | ||
let container = _postcss.default.root({ | ||
nodes: [ | ||
match1[1].clone() | ||
] | ||
}); | ||
container.walkRules((rule)=>{ | ||
if (inKeyframes(rule)) return; | ||
rule.selector = (0, _formatVariantSelector).finalizeSelector(finalFormat, { | ||
selector: rule.selector, | ||
candidate, | ||
context | ||
}); | ||
}); | ||
match1[1] = container.nodes[0]; | ||
match = applyFinalFormat(match, { | ||
context, | ||
candidate | ||
}); | ||
// Skip rules with invalid selectors | ||
// This will cause the candidate to be added to the "not class" | ||
// cache skipping it entirely for future builds | ||
if (match === null) { | ||
continue; | ||
} | ||
yield match1; | ||
yield match; | ||
} | ||
} | ||
} | ||
exports.resolveMatches = resolveMatches; | ||
function applyFinalFormat(match, { context , candidate }) { | ||
if (!match[0].collectedFormats) { | ||
return match; | ||
} | ||
let isValid = true; | ||
let finalFormat; | ||
try { | ||
finalFormat = (0, _formatVariantSelector.formatVariantSelector)(match[0].collectedFormats, { | ||
context, | ||
candidate | ||
}); | ||
} catch { | ||
// The format selector we produced is invalid | ||
// This could be because: | ||
// - A bug exists | ||
// - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`) | ||
// - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`) | ||
// Either way the build will fail because of this | ||
// We would rather that the build pass "silently" given that this could | ||
// happen because of picking up invalid things when scanning content | ||
// So we'll throw out the candidate instead | ||
return null; | ||
} | ||
let container = _postcss.default.root({ | ||
nodes: [ | ||
match[1].clone() | ||
] | ||
}); | ||
container.walkRules((rule)=>{ | ||
if (inKeyframes(rule)) { | ||
return; | ||
} | ||
try { | ||
let selector = (0, _formatVariantSelector.finalizeSelector)(rule.selector, finalFormat, { | ||
candidate, | ||
context | ||
}); | ||
// Finalize Selector determined that this candidate is irrelevant | ||
// TODO: This elimination should happen earlier so this never happens | ||
if (selector === null) { | ||
rule.remove(); | ||
return; | ||
} | ||
rule.selector = selector; | ||
} catch { | ||
// If this selector is invalid we also want to skip it | ||
// But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content | ||
isValid = false; | ||
return false; | ||
} | ||
}); | ||
if (!isValid) { | ||
return null; | ||
} | ||
// If all rules have been eliminated we can skip this candidate entirely | ||
if (container.nodes.length === 0) { | ||
return null; | ||
} | ||
match[1] = container.nodes[0]; | ||
return match; | ||
} | ||
function inKeyframes(rule) { | ||
return rule.parent && rule.parent.type === "atrule" && rule.parent.name === "keyframes"; | ||
} | ||
function generateRules(candidates, context) { | ||
function getImportantStrategy(important) { | ||
if (important === true) { | ||
return (rule)=>{ | ||
if (inKeyframes(rule)) { | ||
return; | ||
} | ||
rule.walkDecls((d)=>{ | ||
if (d.parent.type === "rule" && !inKeyframes(d.parent)) { | ||
d.important = true; | ||
} | ||
}); | ||
}; | ||
} | ||
if (typeof important === "string") { | ||
return (rule)=>{ | ||
if (inKeyframes(rule)) { | ||
return; | ||
} | ||
rule.selectors = rule.selectors.map((selector)=>{ | ||
return (0, _applyImportantSelector.applyImportantSelector)(selector, important); | ||
}); | ||
}; | ||
} | ||
} | ||
function generateRules(candidates, context, isSorting = false) { | ||
let allRules = []; | ||
let strategy = getImportantStrategy(context.tailwindConfig.important); | ||
for (let candidate of candidates){ | ||
@@ -640,4 +893,4 @@ if (context.notClassCache.has(candidate)) { | ||
} | ||
if (context.classCache.has(candidate)) { | ||
allRules.push(context.classCache.get(candidate)); | ||
if (context.candidateRuleCache.has(candidate)) { | ||
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate))); | ||
continue; | ||
@@ -651,26 +904,8 @@ } | ||
context.classCache.set(candidate, matches); | ||
allRules.push(matches); | ||
} | ||
// Strategy based on `tailwindConfig.important` | ||
let strategy = ((important)=>{ | ||
if (important === true) { | ||
return (rule)=>{ | ||
rule.walkDecls((d)=>{ | ||
if (d.parent.type === "rule" && !inKeyframes(d.parent)) { | ||
d.important = true; | ||
} | ||
}); | ||
}; | ||
} | ||
if (typeof important === "string") { | ||
return (rule)=>{ | ||
rule.selectors = rule.selectors.map((selector)=>{ | ||
return `${important} ${selector}`; | ||
}); | ||
}; | ||
} | ||
})(context.tailwindConfig.important); | ||
return allRules.flat(1).map(([{ sort , layer , options }, rule])=>{ | ||
if (options.respectImportant) { | ||
if (strategy) { | ||
var _context_candidateRuleCache_get; | ||
let rules = (_context_candidateRuleCache_get = context.candidateRuleCache.get(candidate)) !== null && _context_candidateRuleCache_get !== void 0 ? _context_candidateRuleCache_get : new Set(); | ||
context.candidateRuleCache.set(candidate, rules); | ||
for (const match of matches){ | ||
let [{ sort , options }, rule] = match; | ||
if (options.respectImportant && strategy) { | ||
let container = _postcss.default.root({ | ||
@@ -681,20 +916,20 @@ nodes: [ | ||
}); | ||
container.walkRules((r)=>{ | ||
if (inKeyframes(r)) { | ||
return; | ||
} | ||
strategy(r); | ||
}); | ||
container.walkRules(strategy); | ||
rule = container.nodes[0]; | ||
} | ||
// Note: We have to clone rules during sorting | ||
// so we eliminate some shared mutable state | ||
let newEntry = [ | ||
sort, | ||
isSorting ? rule.clone() : rule | ||
]; | ||
rules.add(newEntry); | ||
context.ruleCache.add(newEntry); | ||
allRules.push(newEntry); | ||
} | ||
return [ | ||
sort | context.layerOrder[layer], | ||
rule | ||
]; | ||
}); | ||
} | ||
return allRules; | ||
} | ||
exports.generateRules = generateRules; | ||
function isArbitraryValue(input) { | ||
return input.startsWith("[") && input.endsWith("]"); | ||
} |
@@ -5,34 +5,11 @@ "use strict"; | ||
}); | ||
exports.default = getModuleDependencies; | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _path = _interopRequireDefault(require("path")); | ||
var _resolve = _interopRequireDefault(require("resolve")); | ||
var _detective = _interopRequireDefault(require("detective")); | ||
function getModuleDependencies(entryFile) { | ||
const rootModule = createModule(entryFile); | ||
const modules = [ | ||
rootModule | ||
]; | ||
// Iterate over the modules, even when new | ||
// ones are being added | ||
for (const mdl of modules){ | ||
mdl.requires.filter((dep)=>{ | ||
// Only track local modules, not node_modules | ||
return dep.startsWith("./") || dep.startsWith("../"); | ||
}).forEach((dep)=>{ | ||
try { | ||
const basedir = _path.default.dirname(mdl.file); | ||
const depPath = _resolve.default.sync(dep, { | ||
basedir | ||
}); | ||
const depModule = createModule(depPath); | ||
modules.push(depModule); | ||
} catch (_err) { | ||
// eslint-disable-next-line no-empty | ||
} | ||
}); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return getModuleDependencies; | ||
} | ||
return modules; | ||
} | ||
function _interopRequireDefault(obj) { | ||
}); | ||
const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); | ||
const _path = /*#__PURE__*/ _interop_require_default(require("path")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -42,9 +19,83 @@ default: obj | ||
} | ||
function createModule(file) { | ||
const source = _fs.default.readFileSync(file, "utf-8"); | ||
const requires = (0, _detective).default(source); | ||
return { | ||
file, | ||
requires | ||
}; | ||
let jsExtensions = [ | ||
".js", | ||
".cjs", | ||
".mjs" | ||
]; | ||
// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve | ||
// `b.ts` before `b.js` | ||
// | ||
// E.g.: | ||
// | ||
// a.ts | ||
// b // .ts | ||
// c // .ts | ||
// a.js | ||
// b // .js or .ts | ||
let jsResolutionOrder = [ | ||
"", | ||
".js", | ||
".cjs", | ||
".mjs", | ||
".ts", | ||
".cts", | ||
".mts", | ||
".jsx", | ||
".tsx" | ||
]; | ||
let tsResolutionOrder = [ | ||
"", | ||
".ts", | ||
".cts", | ||
".mts", | ||
".tsx", | ||
".js", | ||
".cjs", | ||
".mjs", | ||
".jsx" | ||
]; | ||
function resolveWithExtension(file, extensions) { | ||
// Try to find `./a.ts`, `./a.ts`, ... from `./a` | ||
for (let ext of extensions){ | ||
let full = `${file}${ext}`; | ||
if (_fs.default.existsSync(full) && _fs.default.statSync(full).isFile()) { | ||
return full; | ||
} | ||
} | ||
// Try to find `./a/index.js` from `./a` | ||
for (let ext of extensions){ | ||
let full = `${file}/index${ext}`; | ||
if (_fs.default.existsSync(full)) { | ||
return full; | ||
} | ||
} | ||
return null; | ||
} | ||
function* _getModuleDependencies(filename, base, seen, ext = _path.default.extname(filename)) { | ||
// Try to find the file | ||
let absoluteFile = resolveWithExtension(_path.default.resolve(base, filename), jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder); | ||
if (absoluteFile === null) return; // File doesn't exist | ||
// Prevent infinite loops when there are circular dependencies | ||
if (seen.has(absoluteFile)) return; // Already seen | ||
seen.add(absoluteFile); | ||
// Mark the file as a dependency | ||
yield absoluteFile; | ||
// Resolve new base for new imports/requires | ||
base = _path.default.dirname(absoluteFile); | ||
ext = _path.default.extname(absoluteFile); | ||
let contents = _fs.default.readFileSync(absoluteFile, "utf-8"); | ||
// Find imports/requires | ||
for (let match of [ | ||
...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi), | ||
...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi), | ||
...contents.matchAll(/require\(['"`](.+)['"`]\)/gi) | ||
]){ | ||
// Bail out if it's not a relative file | ||
if (!match[1].startsWith(".")) continue; | ||
yield* _getModuleDependencies(match[1], base, seen, ext); | ||
} | ||
} | ||
function getModuleDependencies(absoluteFilePath) { | ||
if (absoluteFilePath === null) return new Set(); | ||
return new Set(_getModuleDependencies(absoluteFilePath, _path.default.dirname(absoluteFilePath), new Set())); | ||
} |
@@ -5,4 +5,14 @@ "use strict"; | ||
}); | ||
exports.default = normalizeTailwindDirectives; | ||
var _log = _interopRequireDefault(require("../util/log")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return normalizeTailwindDirectives; | ||
} | ||
}); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("../util/log")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function normalizeTailwindDirectives(root) { | ||
@@ -49,3 +59,3 @@ let tailwindDirectives = new Set(); | ||
`Use \`@layer utilities\` or \`@layer components\` instead.`, | ||
"https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer", | ||
"https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer" | ||
]); | ||
@@ -82,6 +92,1 @@ } | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,8 +5,8 @@ "use strict"; | ||
}); | ||
exports.default = expandApplyAtRules; | ||
function expandApplyAtRules() { | ||
return (root)=>{ | ||
partitionRules(root); | ||
}; | ||
} | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return expandApplyAtRules; | ||
} | ||
}); | ||
function partitionRules(root) { | ||
@@ -21,6 +21,6 @@ if (!root.walkAtRules) return; | ||
} | ||
for (let rule1 of applyParents){ | ||
for (let rule of applyParents){ | ||
let nodeGroups = []; | ||
let lastGroup = []; | ||
for (let node of rule1.nodes){ | ||
for (let node of rule.nodes){ | ||
if (node.type === "atrule" && node.name === "apply") { | ||
@@ -47,10 +47,15 @@ if (lastGroup.length > 0) { | ||
].reverse()){ | ||
let clone = rule1.clone({ | ||
let clone = rule.clone({ | ||
nodes: [] | ||
}); | ||
clone.append(group); | ||
rule1.after(clone); | ||
rule.after(clone); | ||
} | ||
rule1.remove(); | ||
rule.remove(); | ||
} | ||
} | ||
function expandApplyAtRules() { | ||
return (root)=>{ | ||
partitionRules(root); | ||
}; | ||
} |
@@ -5,9 +5,31 @@ "use strict"; | ||
}); | ||
exports.pattern = pattern; | ||
exports.withoutCapturing = withoutCapturing; | ||
exports.any = any; | ||
exports.optional = optional; | ||
exports.zeroOrMore = zeroOrMore; | ||
exports.nestedBrackets = nestedBrackets; | ||
exports.escape = escape; | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
pattern: function() { | ||
return pattern; | ||
}, | ||
withoutCapturing: function() { | ||
return withoutCapturing; | ||
}, | ||
any: function() { | ||
return any; | ||
}, | ||
optional: function() { | ||
return optional; | ||
}, | ||
zeroOrMore: function() { | ||
return zeroOrMore; | ||
}, | ||
nestedBrackets: function() { | ||
return nestedBrackets; | ||
}, | ||
escape: function() { | ||
return escape; | ||
} | ||
}); | ||
const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g; | ||
@@ -21,4 +43,3 @@ const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source); | ||
]; | ||
source = source.map((item)=>item instanceof RegExp ? item.source : item | ||
); | ||
source = source.map((item)=>item instanceof RegExp ? item.source : item); | ||
return source.join(""); | ||
@@ -50,3 +71,3 @@ } | ||
/[^\s]*/, | ||
escape(close), | ||
escape(close) | ||
]); | ||
@@ -53,0 +74,0 @@ } |
@@ -5,73 +5,20 @@ "use strict"; | ||
}); | ||
exports.default = resolveDefaultsAtRules; | ||
exports.elementSelectorParser = void 0; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _featureFlags = require("../featureFlags"); | ||
function resolveDefaultsAtRules({ tailwindConfig }) { | ||
return (root)=>{ | ||
let variableNodeMap = new Map(); | ||
/** @type {Set<import('postcss').AtRule>} */ let universals = new Set(); | ||
root.walkAtRules("defaults", (rule)=>{ | ||
if (rule.nodes && rule.nodes.length > 0) { | ||
universals.add(rule); | ||
return; | ||
} | ||
let variable = rule.params; | ||
if (!variableNodeMap.has(variable)) { | ||
variableNodeMap.set(variable, new Set()); | ||
} | ||
variableNodeMap.get(variable).add(rule.parent); | ||
rule.remove(); | ||
}); | ||
for (let universal of universals){ | ||
/** @type {Map<string, Set<string>>} */ let selectorGroups = new Map(); | ||
var ref; | ||
let rules = (ref = variableNodeMap.get(universal.params)) !== null && ref !== void 0 ? ref : []; | ||
for (let rule of rules){ | ||
for (let selector of extractElementSelector(rule.selector)){ | ||
// If selector contains a vendor prefix after a pseudo element or class, | ||
// we consider them separately because merging the declarations into | ||
// a single rule will cause browsers that do not understand the | ||
// vendor prefix to throw out the whole rule | ||
let selectorGroupName = selector.includes(":-") || selector.includes("::-") ? selector : "__DEFAULT__"; | ||
var ref1; | ||
let selectors = (ref1 = selectorGroups.get(selectorGroupName)) !== null && ref1 !== void 0 ? ref1 : new Set(); | ||
selectorGroups.set(selectorGroupName, selectors); | ||
selectors.add(selector); | ||
} | ||
} | ||
if ((0, _featureFlags).flagEnabled(tailwindConfig, "optimizeUniversalDefaults")) { | ||
if (selectorGroups.size === 0) { | ||
universal.remove(); | ||
continue; | ||
} | ||
for (let [, selectors] of selectorGroups){ | ||
let universalRule = _postcss.default.rule({ | ||
source: universal.source | ||
}); | ||
universalRule.selectors = [ | ||
...selectors | ||
]; | ||
universalRule.append(universal.nodes.map((node)=>node.clone() | ||
)); | ||
universal.before(universalRule); | ||
} | ||
} else { | ||
let universalRule = _postcss.default.rule({ | ||
source: universal.source | ||
}); | ||
universalRule.selectors = [ | ||
"*", | ||
"::before", | ||
"::after" | ||
]; | ||
universalRule.append(universal.nodes); | ||
universal.before(universalRule); | ||
} | ||
universal.remove(); | ||
} | ||
}; | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
function _interopRequireDefault(obj) { | ||
_export(exports, { | ||
elementSelectorParser: function() { | ||
return elementSelectorParser; | ||
}, | ||
default: function() { | ||
return resolveDefaultsAtRules; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
const _featureFlags = require("../featureFlags"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -83,3 +30,3 @@ default: obj | ||
id (node) { | ||
return _postcssSelectorParser.default.attribute({ | ||
return _postcssselectorparser.default.attribute({ | ||
attribute: "id", | ||
@@ -114,13 +61,11 @@ operator: "=", | ||
]); | ||
let splitPointIdx = rest.findIndex((n)=>searchFor.has(n.type) | ||
); | ||
let splitPointIdx = rest.findIndex((n)=>searchFor.has(n.type)); | ||
if (splitPointIdx === -1) return rest.reverse().join("").trim(); | ||
let node1 = rest[splitPointIdx]; | ||
let bestNode = getNode[node1.type] ? getNode[node1.type](node1) : node1; | ||
let node = rest[splitPointIdx]; | ||
let bestNode = getNode[node.type] ? getNode[node.type](node) : node; | ||
rest = rest.slice(0, splitPointIdx); | ||
let combinatorIdx = rest.findIndex((n)=>n.type === "combinator" && n.value === ">" | ||
); | ||
let combinatorIdx = rest.findIndex((n)=>n.type === "combinator" && n.value === ">"); | ||
if (combinatorIdx !== -1) { | ||
rest.splice(0, combinatorIdx); | ||
rest.unshift(_postcssSelectorParser.default.universal()); | ||
rest.unshift(_postcssselectorparser.default.universal()); | ||
} | ||
@@ -132,10 +77,8 @@ return [ | ||
} | ||
let elementSelectorParser = (0, _postcssSelectorParser).default((selectors)=>{ | ||
let elementSelectorParser = (0, _postcssselectorparser.default)((selectors)=>{ | ||
return selectors.map((s)=>{ | ||
let nodes = s.split((n)=>n.type === "combinator" && n.value === " " | ||
).pop(); | ||
let nodes = s.split((n)=>n.type === "combinator" && n.value === " ").pop(); | ||
return minimumImpactSelector(nodes); | ||
}); | ||
}); | ||
exports.elementSelectorParser = elementSelectorParser; | ||
let cache = new Map(); | ||
@@ -148,1 +91,82 @@ function extractElementSelector(selector) { | ||
} | ||
function resolveDefaultsAtRules({ tailwindConfig }) { | ||
return (root)=>{ | ||
let variableNodeMap = new Map(); | ||
/** @type {Set<import('postcss').AtRule>} */ let universals = new Set(); | ||
root.walkAtRules("defaults", (rule)=>{ | ||
if (rule.nodes && rule.nodes.length > 0) { | ||
universals.add(rule); | ||
return; | ||
} | ||
let variable = rule.params; | ||
if (!variableNodeMap.has(variable)) { | ||
variableNodeMap.set(variable, new Set()); | ||
} | ||
variableNodeMap.get(variable).add(rule.parent); | ||
rule.remove(); | ||
}); | ||
if ((0, _featureFlags.flagEnabled)(tailwindConfig, "optimizeUniversalDefaults")) { | ||
for (let universal of universals){ | ||
/** @type {Map<string, Set<string>>} */ let selectorGroups = new Map(); | ||
var _variableNodeMap_get; | ||
let rules = (_variableNodeMap_get = variableNodeMap.get(universal.params)) !== null && _variableNodeMap_get !== void 0 ? _variableNodeMap_get : []; | ||
for (let rule of rules){ | ||
for (let selector of extractElementSelector(rule.selector)){ | ||
// If selector contains a vendor prefix after a pseudo element or class, | ||
// we consider them separately because merging the declarations into | ||
// a single rule will cause browsers that do not understand the | ||
// vendor prefix to throw out the whole rule | ||
// Additionally if a selector contains `:has` we also consider | ||
// it separately because FF only recently gained support for it | ||
let selectorGroupName = selector.includes(":-") || selector.includes("::-") || selector.includes(":has") ? selector : "__DEFAULT__"; | ||
var _selectorGroups_get; | ||
let selectors = (_selectorGroups_get = selectorGroups.get(selectorGroupName)) !== null && _selectorGroups_get !== void 0 ? _selectorGroups_get : new Set(); | ||
selectorGroups.set(selectorGroupName, selectors); | ||
selectors.add(selector); | ||
} | ||
} | ||
if ((0, _featureFlags.flagEnabled)(tailwindConfig, "optimizeUniversalDefaults")) { | ||
if (selectorGroups.size === 0) { | ||
universal.remove(); | ||
continue; | ||
} | ||
for (let [, selectors] of selectorGroups){ | ||
let universalRule = _postcss.default.rule({ | ||
source: universal.source | ||
}); | ||
universalRule.selectors = [ | ||
...selectors | ||
]; | ||
universalRule.append(universal.nodes.map((node)=>node.clone())); | ||
universal.before(universalRule); | ||
} | ||
} | ||
universal.remove(); | ||
} | ||
} else if (universals.size) { | ||
let universalRule = _postcss.default.rule({ | ||
selectors: [ | ||
"*", | ||
"::before", | ||
"::after" | ||
] | ||
}); | ||
for (let universal of universals){ | ||
universalRule.append(universal.nodes); | ||
if (!universalRule.parent) { | ||
universal.before(universalRule); | ||
} | ||
if (!universalRule.source) { | ||
universalRule.source = universal.source; | ||
} | ||
universal.remove(); | ||
} | ||
let backdropRule = universalRule.clone({ | ||
selectors: [ | ||
"::backdrop" | ||
] | ||
}); | ||
universalRule.after(backdropRule); | ||
} | ||
}; | ||
} |
@@ -5,29 +5,52 @@ "use strict"; | ||
}); | ||
exports.isValidVariantFormatString = isValidVariantFormatString; | ||
exports.parseVariant = parseVariant; | ||
exports.getFileModifiedMap = getFileModifiedMap; | ||
exports.createContext = createContext; | ||
exports.getContext = getContext; | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _url = _interopRequireDefault(require("url")); | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _dlv = _interopRequireDefault(require("dlv")); | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _transformThemeValue = _interopRequireDefault(require("../util/transformThemeValue")); | ||
var _parseObjectStyles = _interopRequireDefault(require("../util/parseObjectStyles")); | ||
var _prefixSelector = _interopRequireDefault(require("../util/prefixSelector")); | ||
var _isPlainObject = _interopRequireDefault(require("../util/isPlainObject")); | ||
var _escapeClassName = _interopRequireDefault(require("../util/escapeClassName")); | ||
var _nameClass = _interopRequireWildcard(require("../util/nameClass")); | ||
var _pluginUtils = require("../util/pluginUtils"); | ||
var _bigSign = _interopRequireDefault(require("../util/bigSign")); | ||
var _corePlugins = require("../corePlugins"); | ||
var sharedState = _interopRequireWildcard(require("./sharedState")); | ||
var _toPath = require("../util/toPath"); | ||
var _log = _interopRequireDefault(require("../util/log")); | ||
var _negateValue = _interopRequireDefault(require("../util/negateValue")); | ||
var _isValidArbitraryValue = _interopRequireDefault(require("../util/isValidArbitraryValue")); | ||
var _generateRules = require("./generateRules"); | ||
var _cacheInvalidationJs = require("./cacheInvalidation.js"); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
INTERNAL_FEATURES: function() { | ||
return INTERNAL_FEATURES; | ||
}, | ||
isValidVariantFormatString: function() { | ||
return isValidVariantFormatString; | ||
}, | ||
parseVariant: function() { | ||
return parseVariant; | ||
}, | ||
getFileModifiedMap: function() { | ||
return getFileModifiedMap; | ||
}, | ||
createContext: function() { | ||
return createContext; | ||
}, | ||
getContext: function() { | ||
return getContext; | ||
} | ||
}); | ||
const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); | ||
const _url = /*#__PURE__*/ _interop_require_default(require("url")); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _dlv = /*#__PURE__*/ _interop_require_default(require("dlv")); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
const _transformThemeValue = /*#__PURE__*/ _interop_require_default(require("../util/transformThemeValue")); | ||
const _parseObjectStyles = /*#__PURE__*/ _interop_require_default(require("../util/parseObjectStyles")); | ||
const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector")); | ||
const _isPlainObject = /*#__PURE__*/ _interop_require_default(require("../util/isPlainObject")); | ||
const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName")); | ||
const _nameClass = /*#__PURE__*/ _interop_require_wildcard(require("../util/nameClass")); | ||
const _pluginUtils = require("../util/pluginUtils"); | ||
const _corePlugins = require("../corePlugins"); | ||
const _sharedState = /*#__PURE__*/ _interop_require_wildcard(require("./sharedState")); | ||
const _toPath = require("../util/toPath"); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("../util/log")); | ||
const _negateValue = /*#__PURE__*/ _interop_require_default(require("../util/negateValue")); | ||
const _isSyntacticallyValidPropertyValue = /*#__PURE__*/ _interop_require_default(require("../util/isSyntacticallyValidPropertyValue")); | ||
const _generateRules = require("./generateRules"); | ||
const _cacheInvalidation = require("./cacheInvalidation.js"); | ||
const _offsets = require("./offsets.js"); | ||
const _featureFlags = require("../featureFlags.js"); | ||
const _formatVariantSelector = require("../util/formatVariantSelector"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -37,51 +60,108 @@ default: obj | ||
} | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interop_require_wildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
} | ||
let MATCH_VARIANT = Symbol(); | ||
const INTERNAL_FEATURES = Symbol(); | ||
const VARIANT_TYPES = { | ||
AddVariant: Symbol.for("ADD_VARIANT"), | ||
MatchVariant: Symbol.for("MATCH_VARIANT") | ||
}; | ||
const VARIANT_INFO = { | ||
Base: 1 << 0, | ||
Dynamic: 1 << 1 | ||
}; | ||
function prefix(context, selector) { | ||
let prefix1 = context.tailwindConfig.prefix; | ||
return typeof prefix1 === "function" ? prefix1(selector) : prefix1 + selector; | ||
let prefix = context.tailwindConfig.prefix; | ||
return typeof prefix === "function" ? prefix(selector) : prefix + selector; | ||
} | ||
function normalizeOptionTypes({ type ="any" , ...options }) { | ||
let types = [].concat(type); | ||
return { | ||
...options, | ||
types: types.map((type)=>{ | ||
if (Array.isArray(type)) { | ||
return { | ||
type: type[0], | ||
...type[1] | ||
}; | ||
} | ||
return { | ||
type, | ||
preferOnConflict: false | ||
}; | ||
}) | ||
}; | ||
} | ||
function parseVariantFormatString(input) { | ||
if (input.includes("{")) { | ||
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`); | ||
return input.split(/{(.*)}/gim).flatMap((line)=>parseVariantFormatString(line) | ||
).filter(Boolean); | ||
} | ||
return [ | ||
input.trim() | ||
]; | ||
} | ||
function isBalanced(input) { | ||
let count = 0; | ||
for (let char of input){ | ||
if (char === "{") { | ||
count++; | ||
/** @type {string[]} */ let parts = []; | ||
// When parsing whitespace around special characters are insignificant | ||
// However, _inside_ of a variant they could be | ||
// Because the selector could look like this | ||
// @media { &[data-name="foo bar"] } | ||
// This is why we do not skip whitespace | ||
let current = ""; | ||
let depth = 0; | ||
for(let idx = 0; idx < input.length; idx++){ | ||
let char = input[idx]; | ||
if (char === "\\") { | ||
// Escaped characters are not special | ||
current += "\\" + input[++idx]; | ||
} else if (char === "{") { | ||
// Nested rule: start | ||
++depth; | ||
parts.push(current.trim()); | ||
current = ""; | ||
} else if (char === "}") { | ||
if (--count < 0) { | ||
return false // unbalanced | ||
; | ||
// Nested rule: end | ||
if (--depth < 0) { | ||
throw new Error(`Your { and } are unbalanced.`); | ||
} | ||
parts.push(current.trim()); | ||
current = ""; | ||
} else { | ||
// Normal character | ||
current += char; | ||
} | ||
} | ||
return count === 0; | ||
if (current.length > 0) { | ||
parts.push(current.trim()); | ||
} | ||
parts = parts.filter((part)=>part !== ""); | ||
return parts; | ||
} | ||
@@ -109,8 +189,8 @@ function insertInto(list, value, { before =[] } = {}) { | ||
return styles.flatMap((style)=>{ | ||
let isNode = !Array.isArray(style) && !(0, _isPlainObject).default(style); | ||
return isNode ? style : (0, _parseObjectStyles).default(style); | ||
let isNode = !Array.isArray(style) && !(0, _isPlainObject.default)(style); | ||
return isNode ? style : (0, _parseObjectStyles.default)(style); | ||
}); | ||
} | ||
function getClasses(selector, mutate) { | ||
let parser = (0, _postcssSelectorParser).default((selectors)=>{ | ||
let parser = (0, _postcssselectorparser.default)((selectors)=>{ | ||
let allClasses = []; | ||
@@ -127,2 +207,16 @@ if (mutate) { | ||
} | ||
/** | ||
* Ignore everything inside a :not(...). This allows you to write code like | ||
* `div:not(.foo)`. If `.foo` is never found in your code, then we used to | ||
* not generated it. But now we will ignore everything inside a `:not`, so | ||
* that it still gets generated. | ||
* | ||
* @param {selectorParser.Root} selectors | ||
*/ function ignoreNot(selectors) { | ||
selectors.walkPseudos((pseudo)=>{ | ||
if (pseudo.value === ":not") { | ||
pseudo.remove(); | ||
} | ||
}); | ||
} | ||
function extractCandidates(node, state = { | ||
@@ -132,32 +226,19 @@ containsNonOnDemandable: false | ||
let classes = []; | ||
// Handle normal rules | ||
let selectors = []; | ||
if (node.type === "rule") { | ||
// Ignore everything inside a :not(...). This allows you to write code like | ||
// `div:not(.foo)`. If `.foo` is never found in your code, then we used to | ||
// not generated it. But now we will ignore everything inside a `:not`, so | ||
// that it still gets generated. | ||
function ignoreNot(selectors) { | ||
selectors.walkPseudos((pseudo)=>{ | ||
if (pseudo.value === ":not") { | ||
pseudo.remove(); | ||
} | ||
}); | ||
// Handle normal rules | ||
selectors.push(...node.selectors); | ||
} else if (node.type === "atrule") { | ||
// Handle at-rules (which contains nested rules) | ||
node.walkRules((rule)=>selectors.push(...rule.selectors)); | ||
} | ||
for (let selector of selectors){ | ||
let classCandidates = getClasses(selector, ignoreNot); | ||
// At least one of the selectors contains non-"on-demandable" candidates. | ||
if (classCandidates.length === 0) { | ||
state.containsNonOnDemandable = true; | ||
} | ||
for (let selector of node.selectors){ | ||
let classCandidates = getClasses(selector, ignoreNot); | ||
// At least one of the selectors contains non-"on-demandable" candidates. | ||
if (classCandidates.length === 0) { | ||
state.containsNonOnDemandable = true; | ||
} | ||
for (let classCandidate of classCandidates){ | ||
classes.push(classCandidate); | ||
} | ||
for (let classCandidate of classCandidates){ | ||
classes.push(classCandidate); | ||
} | ||
} else if (node.type === "atrule") { | ||
node.walkRules((rule)=>{ | ||
for (let classCandidate of rule.selectors.flatMap((selector)=>getClasses(selector) | ||
)){ | ||
classes.push(classCandidate); | ||
} | ||
}); | ||
} | ||
@@ -178,3 +259,3 @@ if (depth === 0) { | ||
if (containsNonOnDemandableSelectors) { | ||
candidates.unshift(sharedState.NOT_ON_DEMAND); | ||
candidates.unshift(_sharedState.NOT_ON_DEMAND); | ||
} | ||
@@ -202,11 +283,12 @@ // However, it could be that it also contains "on-demandable" candidates. | ||
if (!str.startsWith("@")) { | ||
return ({ format })=>format(str) | ||
; | ||
return ({ format })=>format(str); | ||
} | ||
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str); | ||
return ({ wrap })=>wrap(_postcss.default.atRule({ | ||
let [, name, params] = /@(\S*)( .+|[({].*)?/g.exec(str); | ||
var _params_trim; | ||
return ({ wrap })=>{ | ||
return wrap(_postcss.default.atRule({ | ||
name, | ||
params: params.trim() | ||
})) | ||
; | ||
params: (_params_trim = params === null || params === void 0 ? void 0 : params.trim()) !== null && _params_trim !== void 0 ? _params_trim : "" | ||
})); | ||
}; | ||
}).reverse(); | ||
@@ -219,12 +301,18 @@ return (api)=>{ | ||
} | ||
function buildPluginApi(tailwindConfig, context, { variantList , variantMap , offsets , classList }) { | ||
/** | ||
* | ||
* @param {any} tailwindConfig | ||
* @param {any} context | ||
* @param {object} param2 | ||
* @param {Offsets} param2.offsets | ||
*/ function buildPluginApi(tailwindConfig, context, { variantList , variantMap , offsets , classList }) { | ||
function getConfigValue(path, defaultValue) { | ||
return path ? (0, _dlv).default(tailwindConfig, path, defaultValue) : tailwindConfig; | ||
return path ? (0, _dlv.default)(tailwindConfig, path, defaultValue) : tailwindConfig; | ||
} | ||
function applyConfiguredPrefix(selector) { | ||
return (0, _prefixSelector).default(tailwindConfig.prefix, selector); | ||
return (0, _prefixSelector.default)(tailwindConfig.prefix, selector); | ||
} | ||
function prefixIdentifier(identifier, options) { | ||
if (identifier === sharedState.NOT_ON_DEMAND) { | ||
return sharedState.NOT_ON_DEMAND; | ||
if (identifier === _sharedState.NOT_ON_DEMAND) { | ||
return _sharedState.NOT_ON_DEMAND; | ||
} | ||
@@ -236,31 +324,12 @@ if (!options.respectPrefix) { | ||
} | ||
function resolveThemeValue(path, defaultValue, opts = {}) { | ||
let parts = (0, _toPath.toPath)(path); | ||
let value = getConfigValue([ | ||
"theme", | ||
...parts | ||
], defaultValue); | ||
return (0, _transformThemeValue.default)(parts[0])(value, opts); | ||
} | ||
let variantIdentifier = 0; | ||
let api = { | ||
addVariant (variantName, variantFunctions, options = {}) { | ||
variantFunctions = [].concat(variantFunctions).map((variantFunction)=>{ | ||
if (typeof variantFunction !== "string") { | ||
// Safelist public API functions | ||
return ({ args , modifySelectors , container , separator , wrap , format })=>{ | ||
let result = variantFunction(Object.assign({ | ||
modifySelectors, | ||
container, | ||
separator | ||
}, variantFunction[MATCH_VARIANT] && { | ||
args, | ||
wrap, | ||
format | ||
})); | ||
if (typeof result === "string" && !isValidVariantFormatString(result)) { | ||
throw new Error(`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`); | ||
} | ||
return result; | ||
}; | ||
} | ||
if (!isValidVariantFormatString(variantFunction)) { | ||
throw new Error(`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`); | ||
} | ||
return parseVariant(variantFunction); | ||
}); | ||
insertInto(variantList, variantName, options); | ||
variantMap.set(variantName, variantFunctions); | ||
}, | ||
postcss: _postcss.default, | ||
@@ -270,11 +339,3 @@ prefix: applyConfiguredPrefix, | ||
config: getConfigValue, | ||
theme (path, defaultValue) { | ||
const [pathRoot, ...subPaths] = (0, _toPath).toPath(path); | ||
const value = getConfigValue([ | ||
"theme", | ||
pathRoot, | ||
...subPaths | ||
], defaultValue); | ||
return (0, _transformThemeValue).default(pathRoot)(value); | ||
}, | ||
theme: resolveThemeValue, | ||
corePlugins: (path)=>{ | ||
@@ -296,3 +357,3 @@ if (Array.isArray(tailwindConfig.corePlugins)) { | ||
let prefixedIdentifier = prefixIdentifier(identifier, {}); | ||
let offset = offsets.base++; | ||
let offset = offsets.create("base"); | ||
if (!context.candidateRuleMap.has(prefixedIdentifier)) { | ||
@@ -324,3 +385,3 @@ context.candidateRuleMap.set(prefixedIdentifier, []); | ||
{ | ||
sort: offsets.base++, | ||
sort: offsets.create("defaults"), | ||
layer: "defaults" | ||
@@ -334,2 +395,3 @@ }, | ||
let defaultOptions = { | ||
preserveSource: false, | ||
respectPrefix: true, | ||
@@ -347,3 +409,3 @@ respectImportant: false | ||
{ | ||
sort: offsets.components++, | ||
sort: offsets.create("components"), | ||
layer: "components", | ||
@@ -358,2 +420,3 @@ options | ||
let defaultOptions = { | ||
preserveSource: false, | ||
respectPrefix: true, | ||
@@ -371,3 +434,3 @@ respectImportant: true | ||
{ | ||
sort: offsets.utilities++, | ||
sort: offsets.create("utilities"), | ||
layer: "utilities", | ||
@@ -383,9 +446,10 @@ options | ||
respectPrefix: true, | ||
respectImportant: true | ||
respectImportant: true, | ||
modifiers: false | ||
}; | ||
options = { | ||
options = normalizeOptionTypes({ | ||
...defaultOptions, | ||
...options | ||
}; | ||
let offset = offsets.utilities++; | ||
}); | ||
let offset = offsets.create("utilities"); | ||
for(let identifier in utilities){ | ||
@@ -399,18 +463,33 @@ let prefixedIdentifier = prefixIdentifier(identifier, options); | ||
function wrapped(modifier, { isOnlyPlugin }) { | ||
let { type ="any" } = options; | ||
type = [].concat(type); | ||
let [value, coercedType] = (0, _pluginUtils).coerceValue(type, modifier, options, tailwindConfig); | ||
let [value, coercedType, utilityModifier] = (0, _pluginUtils.coerceValue)(options.types, modifier, options, tailwindConfig); | ||
if (value === undefined) { | ||
return []; | ||
} | ||
if (!type.includes(coercedType) && !isOnlyPlugin) { | ||
return []; | ||
if (!options.types.some(({ type })=>type === coercedType)) { | ||
if (isOnlyPlugin) { | ||
_log.default.warn([ | ||
`Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`, | ||
`You can safely update it to \`${identifier}-${modifier.replace(coercedType + ":", "")}\`.` | ||
]); | ||
} else { | ||
return []; | ||
} | ||
} | ||
if (!(0, _isValidArbitraryValue).default(value)) { | ||
if (!(0, _isSyntacticallyValidPropertyValue.default)(value)) { | ||
return []; | ||
} | ||
let ruleSets = [].concat(rule(value)).filter(Boolean).map((declaration)=>({ | ||
[(0, _nameClass).default(identifier, modifier)]: declaration | ||
}) | ||
); | ||
let extras = { | ||
get modifier () { | ||
if (!options.modifiers) { | ||
_log.default.warn(`modifier-used-without-options-for-${identifier}`, [ | ||
"Your plugin must set `modifiers: true` in its options to support modifiers." | ||
]); | ||
} | ||
return utilityModifier; | ||
} | ||
}; | ||
let modifiersEnabled = (0, _featureFlags.flagEnabled)(tailwindConfig, "generalizedModifiers"); | ||
let ruleSets = [].concat(modifiersEnabled ? rule(value, extras) : rule(value)).filter(Boolean).map((declaration)=>({ | ||
[(0, _nameClass.default)(identifier, modifier)]: declaration | ||
})); | ||
return ruleSets; | ||
@@ -435,9 +514,10 @@ } | ||
respectPrefix: true, | ||
respectImportant: false | ||
respectImportant: false, | ||
modifiers: false | ||
}; | ||
options = { | ||
options = normalizeOptionTypes({ | ||
...defaultOptions, | ||
...options | ||
}; | ||
let offset = offsets.components++; | ||
}); | ||
let offset = offsets.create("components"); | ||
for(let identifier in components){ | ||
@@ -451,13 +531,11 @@ let prefixedIdentifier = prefixIdentifier(identifier, options); | ||
function wrapped(modifier, { isOnlyPlugin }) { | ||
let { type ="any" } = options; | ||
type = [].concat(type); | ||
let [value, coercedType] = (0, _pluginUtils).coerceValue(type, modifier, options, tailwindConfig); | ||
let [value, coercedType, utilityModifier] = (0, _pluginUtils.coerceValue)(options.types, modifier, options, tailwindConfig); | ||
if (value === undefined) { | ||
return []; | ||
} | ||
if (!type.includes(coercedType)) { | ||
if (!options.types.some(({ type })=>type === coercedType)) { | ||
if (isOnlyPlugin) { | ||
_log.default.warn([ | ||
`Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`, | ||
`You can safely update it to \`${identifier}-${modifier.replace(coercedType + ":", "")}\`.`, | ||
`You can safely update it to \`${identifier}-${modifier.replace(coercedType + ":", "")}\`.` | ||
]); | ||
@@ -468,9 +546,19 @@ } else { | ||
} | ||
if (!(0, _isValidArbitraryValue).default(value)) { | ||
if (!(0, _isSyntacticallyValidPropertyValue.default)(value)) { | ||
return []; | ||
} | ||
let ruleSets = [].concat(rule(value)).filter(Boolean).map((declaration)=>({ | ||
[(0, _nameClass).default(identifier, modifier)]: declaration | ||
}) | ||
); | ||
let extras = { | ||
get modifier () { | ||
if (!options.modifiers) { | ||
_log.default.warn(`modifier-used-without-options-for-${identifier}`, [ | ||
"Your plugin must set `modifiers: true` in its options to support modifiers." | ||
]); | ||
} | ||
return utilityModifier; | ||
} | ||
}; | ||
let modifiersEnabled = (0, _featureFlags.flagEnabled)(tailwindConfig, "generalizedModifiers"); | ||
let ruleSets = [].concat(modifiersEnabled ? rule(value, extras) : rule(value)).filter(Boolean).map((declaration)=>({ | ||
[(0, _nameClass.default)(identifier, modifier)]: declaration | ||
})); | ||
return ruleSets; | ||
@@ -492,23 +580,82 @@ } | ||
}, | ||
matchVariant: function(variants, options) { | ||
for(let variant in variants){ | ||
var ref; | ||
for (let [k, v] of Object.entries((ref = options === null || options === void 0 ? void 0 : options.values) !== null && ref !== void 0 ? ref : {})){ | ||
api.addVariant(`${variant}-${k}`, variants[variant](v)); | ||
addVariant (variantName, variantFunctions, options = {}) { | ||
variantFunctions = [].concat(variantFunctions).map((variantFunction)=>{ | ||
if (typeof variantFunction !== "string") { | ||
// Safelist public API functions | ||
return (api = {})=>{ | ||
let { args , modifySelectors , container , separator , wrap , format } = api; | ||
let result = variantFunction(Object.assign({ | ||
modifySelectors, | ||
container, | ||
separator | ||
}, options.type === VARIANT_TYPES.MatchVariant && { | ||
args, | ||
wrap, | ||
format | ||
})); | ||
if (typeof result === "string" && !isValidVariantFormatString(result)) { | ||
throw new Error(`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`); | ||
} | ||
if (Array.isArray(result)) { | ||
return result.filter((variant)=>typeof variant === "string").map((variant)=>parseVariant(variant)); | ||
} | ||
// result may be undefined with legacy variants that use APIs like `modifySelectors` | ||
// result may also be a postcss node if someone was returning the result from `modifySelectors` | ||
return result && typeof result === "string" && parseVariant(result)(api); | ||
}; | ||
} | ||
api.addVariant(variant, Object.assign(({ args , wrap })=>{ | ||
let formatString = variants[variant](args); | ||
if (!formatString) return null; | ||
if (!formatString.startsWith("@")) { | ||
return formatString; | ||
} | ||
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(formatString); | ||
return wrap(_postcss.default.atRule({ | ||
name, | ||
params: params.trim() | ||
})); | ||
if (!isValidVariantFormatString(variantFunction)) { | ||
throw new Error(`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`); | ||
} | ||
return parseVariant(variantFunction); | ||
}); | ||
insertInto(variantList, variantName, options); | ||
variantMap.set(variantName, variantFunctions); | ||
context.variantOptions.set(variantName, options); | ||
}, | ||
matchVariant (variant, variantFn, options) { | ||
var _options_id; | ||
// A unique identifier that "groups" these variants together. | ||
// This is for internal use only which is why it is not present in the types | ||
let id = (_options_id = options === null || options === void 0 ? void 0 : options.id) !== null && _options_id !== void 0 ? _options_id : ++variantIdentifier; | ||
let isSpecial = variant === "@"; | ||
let modifiersEnabled = (0, _featureFlags.flagEnabled)(tailwindConfig, "generalizedModifiers"); | ||
var _options_values; | ||
for (let [key, value] of Object.entries((_options_values = options === null || options === void 0 ? void 0 : options.values) !== null && _options_values !== void 0 ? _options_values : {})){ | ||
if (key === "DEFAULT") continue; | ||
api.addVariant(isSpecial ? `${variant}${key}` : `${variant}-${key}`, ({ args , container })=>{ | ||
return variantFn(value, modifiersEnabled ? { | ||
modifier: args === null || args === void 0 ? void 0 : args.modifier, | ||
container | ||
} : { | ||
container | ||
}); | ||
}, { | ||
[MATCH_VARIANT]: true | ||
}), options); | ||
...options, | ||
value, | ||
id, | ||
type: VARIANT_TYPES.MatchVariant, | ||
variantInfo: VARIANT_INFO.Base | ||
}); | ||
} | ||
var _options_values1; | ||
let hasDefault = "DEFAULT" in ((_options_values1 = options === null || options === void 0 ? void 0 : options.values) !== null && _options_values1 !== void 0 ? _options_values1 : {}); | ||
api.addVariant(variant, ({ args , container })=>{ | ||
if ((args === null || args === void 0 ? void 0 : args.value) === _sharedState.NONE && !hasDefault) { | ||
return null; | ||
} | ||
var // (JetBrains) plugins. | ||
_args_value; | ||
return variantFn((args === null || args === void 0 ? void 0 : args.value) === _sharedState.NONE ? options.values.DEFAULT : (_args_value = args === null || args === void 0 ? void 0 : args.value) !== null && _args_value !== void 0 ? _args_value : typeof args === "string" ? args : "", modifiersEnabled ? { | ||
modifier: args === null || args === void 0 ? void 0 : args.modifier, | ||
container | ||
} : { | ||
container | ||
}); | ||
}, { | ||
...options, | ||
id, | ||
type: VARIANT_TYPES.MatchVariant, | ||
variantInfo: VARIANT_INFO.Dynamic | ||
}); | ||
} | ||
@@ -527,4 +674,5 @@ }; | ||
let changed = false; | ||
let mtimesToCommit = new Map(); | ||
for (let file of files){ | ||
var ref; | ||
var _fs_statSync; | ||
if (!file) continue; | ||
@@ -534,5 +682,5 @@ let parsed = _url.default.parse(file); | ||
pathname = parsed.search ? pathname.replace(parsed.search, "") : pathname; | ||
let newModified = (ref = _fs.default.statSync(decodeURIComponent(pathname), { | ||
let newModified = (_fs_statSync = _fs.default.statSync(decodeURIComponent(pathname), { | ||
throwIfNoEntry: false | ||
})) === null || ref === void 0 ? void 0 : ref.mtimeMs; | ||
})) === null || _fs_statSync === void 0 ? void 0 : _fs_statSync.mtimeMs; | ||
if (!newModified) { | ||
@@ -544,5 +692,8 @@ continue; | ||
} | ||
fileModifiedMap.set(file, newModified); | ||
mtimesToCommit.set(file, newModified); | ||
} | ||
return changed; | ||
return [ | ||
changed, | ||
mtimesToCommit | ||
]; | ||
} | ||
@@ -588,3 +739,4 @@ function extractVariantAtRules(node) { | ||
addComponents(node, { | ||
respectPrefix: false | ||
respectPrefix: false, | ||
preserveSource: true | ||
}); | ||
@@ -598,3 +750,4 @@ }); | ||
addUtilities(node, { | ||
respectPrefix: false | ||
respectPrefix: false, | ||
preserveSource: true | ||
}); | ||
@@ -628,13 +781,36 @@ }); | ||
let beforeVariants = [ | ||
_corePlugins.variantPlugins["childVariant"], | ||
_corePlugins.variantPlugins["pseudoElementVariants"], | ||
_corePlugins.variantPlugins["pseudoClassVariants"], | ||
_corePlugins.variantPlugins["pseudoClassVariants"], | ||
_corePlugins.variantPlugins["hasVariants"], | ||
_corePlugins.variantPlugins["ariaVariants"], | ||
_corePlugins.variantPlugins["dataVariants"] | ||
]; | ||
let afterVariants = [ | ||
_corePlugins.variantPlugins["supportsVariants"], | ||
_corePlugins.variantPlugins["reducedMotionVariants"], | ||
_corePlugins.variantPlugins["prefersContrastVariants"], | ||
_corePlugins.variantPlugins["screenVariants"], | ||
_corePlugins.variantPlugins["orientationVariants"], | ||
_corePlugins.variantPlugins["directionVariants"], | ||
_corePlugins.variantPlugins["reducedMotionVariants"], | ||
_corePlugins.variantPlugins["darkVariants"], | ||
_corePlugins.variantPlugins["printVariant"], | ||
_corePlugins.variantPlugins["screenVariants"], | ||
_corePlugins.variantPlugins["orientationVariants"], | ||
_corePlugins.variantPlugins["forcedColorsVariants"], | ||
_corePlugins.variantPlugins["printVariant"] | ||
]; | ||
// This is a compatibility fix for the pre 3.4 dark mode behavior | ||
// `class` retains the old behavior, but `selector` keeps the new behavior | ||
let isLegacyDarkMode = context.tailwindConfig.darkMode === "class" || Array.isArray(context.tailwindConfig.darkMode) && context.tailwindConfig.darkMode[0] === "class"; | ||
if (isLegacyDarkMode) { | ||
afterVariants = [ | ||
_corePlugins.variantPlugins["supportsVariants"], | ||
_corePlugins.variantPlugins["reducedMotionVariants"], | ||
_corePlugins.variantPlugins["prefersContrastVariants"], | ||
_corePlugins.variantPlugins["darkVariants"], | ||
_corePlugins.variantPlugins["screenVariants"], | ||
_corePlugins.variantPlugins["orientationVariants"], | ||
_corePlugins.variantPlugins["directionVariants"], | ||
_corePlugins.variantPlugins["forcedColorsVariants"], | ||
_corePlugins.variantPlugins["printVariant"] | ||
]; | ||
} | ||
return [ | ||
@@ -651,9 +827,5 @@ ...corePluginList, | ||
let variantMap = new Map(); | ||
let offsets = { | ||
defaults: 0n, | ||
base: 0n, | ||
components: 0n, | ||
utilities: 0n, | ||
user: 0n | ||
}; | ||
context.variantMap = variantMap; | ||
let offsets = new _offsets.Offsets(); | ||
context.offsets = offsets; | ||
let classList = new Set(); | ||
@@ -675,54 +847,19 @@ let pluginApi = buildPluginApi(context.tailwindConfig, context, { | ||
} | ||
let highestOffset = ((args)=>args.reduce((m, e)=>e > m ? e : m | ||
) | ||
)([ | ||
offsets.base, | ||
offsets.defaults, | ||
offsets.components, | ||
offsets.utilities, | ||
offsets.user, | ||
]); | ||
let reservedBits = BigInt(highestOffset.toString(2).length); | ||
// A number one less than the top range of the highest offset area | ||
// so arbitrary properties are always sorted at the end. | ||
context.arbitraryPropertiesSort = (1n << reservedBits << 0n) - 1n; | ||
context.layerOrder = { | ||
defaults: 1n << reservedBits << 0n, | ||
base: 1n << reservedBits << 1n, | ||
components: 1n << reservedBits << 2n, | ||
utilities: 1n << reservedBits << 3n, | ||
user: 1n << reservedBits << 4n | ||
}; | ||
reservedBits += 5n; | ||
let offset = 0; | ||
context.variantOrder = new Map(variantList.map((variant, i)=>{ | ||
let variantFunctions = variantMap.get(variant).length; | ||
let bits = 1n << BigInt(i + offset) << reservedBits; | ||
offset += variantFunctions - 1; | ||
return [ | ||
variant, | ||
bits | ||
]; | ||
}).sort(([, a], [, z])=>(0, _bigSign).default(a - z) | ||
)); | ||
context.minimumScreen = [ | ||
...context.variantOrder.values() | ||
].shift(); | ||
// Make sure to record bit masks for every variant | ||
offsets.recordVariants(variantList, (variant)=>variantMap.get(variant).length); | ||
// Build variantMap | ||
for (let [variantName, variantFunctions1] of variantMap.entries()){ | ||
let sort = context.variantOrder.get(variantName); | ||
context.variantMap.set(variantName, variantFunctions1.map((variantFunction, idx)=>[ | ||
sort << BigInt(idx), | ||
for (let [variantName, variantFunctions] of variantMap.entries()){ | ||
context.variantMap.set(variantName, variantFunctions.map((variantFunction, idx)=>[ | ||
offsets.forVariant(variantName, idx), | ||
variantFunction | ||
] | ||
)); | ||
])); | ||
} | ||
var _safelist; | ||
let safelist = ((_safelist = context.tailwindConfig.safelist) !== null && _safelist !== void 0 ? _safelist : []).filter(Boolean); | ||
var _context_tailwindConfig_safelist; | ||
let safelist = ((_context_tailwindConfig_safelist = context.tailwindConfig.safelist) !== null && _context_tailwindConfig_safelist !== void 0 ? _context_tailwindConfig_safelist : []).filter(Boolean); | ||
if (safelist.length > 0) { | ||
let checks = []; | ||
for (let value1 of safelist){ | ||
if (typeof value1 === "string") { | ||
for (let value of safelist){ | ||
if (typeof value === "string") { | ||
context.changedContent.push({ | ||
content: value1, | ||
content: value, | ||
extension: "html" | ||
@@ -732,11 +869,11 @@ }); | ||
} | ||
if (value1 instanceof RegExp) { | ||
if (value instanceof RegExp) { | ||
_log.default.warn("root-regex", [ | ||
"Regular expressions in `safelist` work differently in Tailwind CSS v3.0.", | ||
"Update your `safelist` configuration to eliminate this warning.", | ||
"https://tailwindcss.com/docs/content-configuration#safelisting-classes", | ||
"https://tailwindcss.com/docs/content-configuration#safelisting-classes" | ||
]); | ||
continue; | ||
} | ||
checks.push(value1); | ||
checks.push(value); | ||
} | ||
@@ -746,9 +883,9 @@ if (checks.length > 0) { | ||
let prefixLength = context.tailwindConfig.prefix.length; | ||
let checkImportantUtils = checks.some((check)=>check.pattern.source.includes("!")); | ||
for (let util of classList){ | ||
let utils = Array.isArray(util) ? (()=>{ | ||
let [utilName, options] = util; | ||
var ref; | ||
let values = Object.keys((ref = options === null || options === void 0 ? void 0 : options.values) !== null && ref !== void 0 ? ref : {}); | ||
let classes = values.map((value)=>(0, _nameClass).formatClass(utilName, value) | ||
); | ||
var _options_values; | ||
let values = Object.keys((_options_values = options === null || options === void 0 ? void 0 : options.values) !== null && _options_values !== void 0 ? _options_values : {}); | ||
let classes = values.map((value)=>(0, _nameClass.formatClass)(utilName, value)); | ||
if (options === null || options === void 0 ? void 0 : options.supportsNegativeValues) { | ||
@@ -759,4 +896,3 @@ // This is the normal negated version | ||
...classes, | ||
...classes.map((cls)=>"-" + cls | ||
) | ||
...classes.map((cls)=>"-" + cls) | ||
]; | ||
@@ -769,6 +905,17 @@ // This is the negated version *after* the prefix | ||
...classes, | ||
...classes.map((cls)=>cls.slice(0, prefixLength) + "-" + cls.slice(prefixLength) | ||
), | ||
...classes.map((cls)=>cls.slice(0, prefixLength) + "-" + cls.slice(prefixLength)) | ||
]; | ||
} | ||
if (options.types.some(({ type })=>type === "color")) { | ||
classes = [ | ||
...classes, | ||
...classes.flatMap((cls)=>Object.keys(context.tailwindConfig.theme.opacity).map((opacity)=>`${cls}/${opacity}`)) | ||
]; | ||
} | ||
if (checkImportantUtils && (options === null || options === void 0 ? void 0 : options.respectImportant)) { | ||
classes = [ | ||
...classes, | ||
...classes.map((cls)=>"!" + cls) | ||
]; | ||
} | ||
return classes; | ||
@@ -778,3 +925,3 @@ })() : [ | ||
]; | ||
for (let util1 of utils){ | ||
for (let util of utils){ | ||
for (let { pattern , variants =[] } of checks){ | ||
@@ -787,6 +934,6 @@ // RegExp with the /g flag are stateful, so let's reset the last | ||
} | ||
if (!pattern.test(util1)) continue; | ||
if (!pattern.test(util)) continue; | ||
patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1); | ||
context.changedContent.push({ | ||
content: util1, | ||
content: util, | ||
extension: "html" | ||
@@ -796,3 +943,3 @@ }); | ||
context.changedContent.push({ | ||
content: variant + context.tailwindConfig.separator + util1, | ||
content: variant + context.tailwindConfig.separator + util, | ||
extension: "html" | ||
@@ -809,3 +956,3 @@ }); | ||
"Fix this pattern or remove it from your `safelist` configuration.", | ||
"https://tailwindcss.com/docs/content-configuration#safelisting-classes", | ||
"https://tailwindcss.com/docs/content-configuration#safelisting-classes" | ||
]); | ||
@@ -815,24 +962,48 @@ } | ||
} | ||
var _context_tailwindConfig_darkMode, _concat_; | ||
let darkClassName = (_concat_ = [].concat((_context_tailwindConfig_darkMode = context.tailwindConfig.darkMode) !== null && _context_tailwindConfig_darkMode !== void 0 ? _context_tailwindConfig_darkMode : "media")[1]) !== null && _concat_ !== void 0 ? _concat_ : "dark"; | ||
// A list of utilities that are used by certain Tailwind CSS utilities but | ||
// that don't exist on their own. This will result in them "not existing" and | ||
// sorting could be weird since you still require them in order to make the | ||
// host utitlies work properly. (Thanks Biology) | ||
let parasiteUtilities = new Set([ | ||
// host utilities work properly. (Thanks Biology) | ||
let parasiteUtilities = [ | ||
prefix(context, darkClassName), | ||
prefix(context, "group"), | ||
prefix(context, "peer") | ||
]); | ||
]; | ||
context.getClassOrder = function getClassOrder(classes) { | ||
let sortedClassNames = new Map(); | ||
for (let [sort, rule] of (0, _generateRules).generateRules(new Set(classes), context)){ | ||
if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue; | ||
sortedClassNames.set(rule.raws.tailwind.candidate, sort); | ||
// Sort classes so they're ordered in a deterministic manner | ||
let sorted = [ | ||
...classes | ||
].sort((a, z)=>{ | ||
if (a === z) return 0; | ||
if (a < z) return -1; | ||
return 1; | ||
}); | ||
// Non-util classes won't be generated, so we default them to null | ||
let sortedClassNames = new Map(sorted.map((className)=>[ | ||
className, | ||
null | ||
])); | ||
// Sort all classes in order | ||
// Non-tailwind classes won't be generated and will be left as `null` | ||
let rules = (0, _generateRules.generateRules)(new Set(sorted), context, true); | ||
rules = context.offsets.sort(rules); | ||
let idx = BigInt(parasiteUtilities.length); | ||
for (const [, rule] of rules){ | ||
let candidate = rule.raws.tailwind.candidate; | ||
var _sortedClassNames_get; | ||
// When multiple rules match a candidate | ||
// always take the position of the first one | ||
sortedClassNames.set(candidate, (_sortedClassNames_get = sortedClassNames.get(candidate)) !== null && _sortedClassNames_get !== void 0 ? _sortedClassNames_get : idx++); | ||
} | ||
return classes.map((className)=>{ | ||
var ref; | ||
let order = (ref = sortedClassNames.get(className)) !== null && ref !== void 0 ? ref : null; | ||
if (order === null && parasiteUtilities.has(className)) { | ||
var _sortedClassNames_get; | ||
let order = (_sortedClassNames_get = sortedClassNames.get(className)) !== null && _sortedClassNames_get !== void 0 ? _sortedClassNames_get : null; | ||
let parasiteIndex = parasiteUtilities.indexOf(className); | ||
if (order === null && parasiteIndex !== -1) { | ||
// This will make sure that it is at the very beginning of the | ||
// `components` layer which technically means 'before any | ||
// components'. | ||
order = context.layerOrder.components; | ||
order = BigInt(parasiteIndex); | ||
} | ||
@@ -847,14 +1018,37 @@ return [ | ||
// ['uppercase', 'lowercase', ...] | ||
context.getClassList = function getClassList() { | ||
context.getClassList = function getClassList(options = {}) { | ||
let output = []; | ||
for (let util of classList){ | ||
if (Array.isArray(util)) { | ||
let [utilName, options] = util; | ||
var _utilOptions_types; | ||
let [utilName, utilOptions] = util; | ||
let negativeClasses = []; | ||
var ref; | ||
for (let [key, value] of Object.entries((ref = options === null || options === void 0 ? void 0 : options.values) !== null && ref !== void 0 ? ref : {})){ | ||
output.push((0, _nameClass).formatClass(utilName, key)); | ||
if ((options === null || options === void 0 ? void 0 : options.supportsNegativeValues) && (0, _negateValue).default(value)) { | ||
negativeClasses.push((0, _nameClass).formatClass(utilName, `-${key}`)); | ||
var _utilOptions_modifiers; | ||
let modifiers = Object.keys((_utilOptions_modifiers = utilOptions === null || utilOptions === void 0 ? void 0 : utilOptions.modifiers) !== null && _utilOptions_modifiers !== void 0 ? _utilOptions_modifiers : {}); | ||
if (utilOptions === null || utilOptions === void 0 ? void 0 : (_utilOptions_types = utilOptions.types) === null || _utilOptions_types === void 0 ? void 0 : _utilOptions_types.some(({ type })=>type === "color")) { | ||
var _context_tailwindConfig_theme_opacity; | ||
modifiers.push(...Object.keys((_context_tailwindConfig_theme_opacity = context.tailwindConfig.theme.opacity) !== null && _context_tailwindConfig_theme_opacity !== void 0 ? _context_tailwindConfig_theme_opacity : {})); | ||
} | ||
let metadata = { | ||
modifiers | ||
}; | ||
let includeMetadata = options.includeMetadata && modifiers.length > 0; | ||
var _utilOptions_values; | ||
for (let [key, value] of Object.entries((_utilOptions_values = utilOptions === null || utilOptions === void 0 ? void 0 : utilOptions.values) !== null && _utilOptions_values !== void 0 ? _utilOptions_values : {})){ | ||
// Ignore undefined and null values | ||
if (value == null) { | ||
continue; | ||
} | ||
let cls = (0, _nameClass.formatClass)(utilName, key); | ||
output.push(includeMetadata ? [ | ||
cls, | ||
metadata | ||
] : cls); | ||
if ((utilOptions === null || utilOptions === void 0 ? void 0 : utilOptions.supportsNegativeValues) && (0, _negateValue.default)(value)) { | ||
let cls = (0, _nameClass.formatClass)(utilName, `-${key}`); | ||
negativeClasses.push(includeMetadata ? [ | ||
cls, | ||
metadata | ||
] : cls); | ||
} | ||
} | ||
@@ -868,10 +1062,190 @@ output.push(...negativeClasses); | ||
}; | ||
// Generate a list of available variants with meta information of the type of variant. | ||
context.getVariants = function getVariants() { | ||
// We use a unique, random ID for candidate names to avoid conflicts | ||
// We can't use characters like `_`, `:`, `@` or `.` because they might | ||
// be used as a separator | ||
let id = Math.random().toString(36).substring(7).toUpperCase(); | ||
let result = []; | ||
for (let [name, options] of context.variantOptions.entries()){ | ||
if (options.variantInfo === VARIANT_INFO.Base) continue; | ||
var _options_values; | ||
result.push({ | ||
name, | ||
isArbitrary: options.type === Symbol.for("MATCH_VARIANT"), | ||
values: Object.keys((_options_values = options.values) !== null && _options_values !== void 0 ? _options_values : {}), | ||
hasDash: name !== "@", | ||
selectors ({ modifier , value } = {}) { | ||
let candidate = `TAILWINDPLACEHOLDER${id}`; | ||
let rule = _postcss.default.rule({ | ||
selector: `.${candidate}` | ||
}); | ||
let container = _postcss.default.root({ | ||
nodes: [ | ||
rule.clone() | ||
] | ||
}); | ||
let before = container.toString(); | ||
var _context_variantMap_get; | ||
let fns = ((_context_variantMap_get = context.variantMap.get(name)) !== null && _context_variantMap_get !== void 0 ? _context_variantMap_get : []).flatMap(([_, fn])=>fn); | ||
let formatStrings = []; | ||
for (let fn of fns){ | ||
var _options_values; | ||
let localFormatStrings = []; | ||
var _options_values_value; | ||
let api = { | ||
args: { | ||
modifier, | ||
value: (_options_values_value = (_options_values = options.values) === null || _options_values === void 0 ? void 0 : _options_values[value]) !== null && _options_values_value !== void 0 ? _options_values_value : value | ||
}, | ||
separator: context.tailwindConfig.separator, | ||
modifySelectors (modifierFunction) { | ||
// Run the modifierFunction over each rule | ||
container.each((rule)=>{ | ||
if (rule.type !== "rule") { | ||
return; | ||
} | ||
rule.selectors = rule.selectors.map((selector)=>{ | ||
return modifierFunction({ | ||
get className () { | ||
return (0, _generateRules.getClassNameFromSelector)(selector); | ||
}, | ||
selector | ||
}); | ||
}); | ||
}); | ||
return container; | ||
}, | ||
format (str) { | ||
localFormatStrings.push(str); | ||
}, | ||
wrap (wrapper) { | ||
localFormatStrings.push(`@${wrapper.name} ${wrapper.params} { & }`); | ||
}, | ||
container | ||
}; | ||
let ruleWithVariant = fn(api); | ||
if (localFormatStrings.length > 0) { | ||
formatStrings.push(localFormatStrings); | ||
} | ||
if (Array.isArray(ruleWithVariant)) { | ||
for (let variantFunction of ruleWithVariant){ | ||
localFormatStrings = []; | ||
variantFunction(api); | ||
formatStrings.push(localFormatStrings); | ||
} | ||
} | ||
} | ||
// Reverse engineer the result of the `container` | ||
let manualFormatStrings = []; | ||
let after = container.toString(); | ||
if (before !== after) { | ||
// Figure out all selectors | ||
container.walkRules((rule)=>{ | ||
let modified = rule.selector; | ||
// Rebuild the base selector, this is what plugin authors would do | ||
// as well. E.g.: `${variant}${separator}${className}`. | ||
// However, plugin authors probably also prepend or append certain | ||
// classes, pseudos, ids, ... | ||
let rebuiltBase = (0, _postcssselectorparser.default)((selectors)=>{ | ||
selectors.walkClasses((classNode)=>{ | ||
classNode.value = `${name}${context.tailwindConfig.separator}${classNode.value}`; | ||
}); | ||
}).processSync(modified); | ||
// Now that we know the original selector, the new selector, and | ||
// the rebuild part in between, we can replace the part that plugin | ||
// authors need to rebuild with `&`, and eventually store it in the | ||
// collectedFormats. Similar to what `format('...')` would do. | ||
// | ||
// E.g.: | ||
// variant: foo | ||
// selector: .markdown > p | ||
// modified (by plugin): .foo .foo\\:markdown > p | ||
// rebuiltBase (internal): .foo\\:markdown > p | ||
// format: .foo & | ||
manualFormatStrings.push(modified.replace(rebuiltBase, "&").replace(candidate, "&")); | ||
}); | ||
// Figure out all atrules | ||
container.walkAtRules((atrule)=>{ | ||
manualFormatStrings.push(`@${atrule.name} (${atrule.params}) { & }`); | ||
}); | ||
} | ||
var _options_values1; | ||
let isArbitraryVariant = !(value in ((_options_values1 = options.values) !== null && _options_values1 !== void 0 ? _options_values1 : {})); | ||
var _options_INTERNAL_FEATURES; | ||
let internalFeatures = (_options_INTERNAL_FEATURES = options[INTERNAL_FEATURES]) !== null && _options_INTERNAL_FEATURES !== void 0 ? _options_INTERNAL_FEATURES : {}; | ||
let respectPrefix = (()=>{ | ||
if (isArbitraryVariant) return false; | ||
if (internalFeatures.respectPrefix === false) return false; | ||
return true; | ||
})(); | ||
formatStrings = formatStrings.map((format)=>format.map((str)=>({ | ||
format: str, | ||
respectPrefix | ||
}))); | ||
manualFormatStrings = manualFormatStrings.map((format)=>({ | ||
format, | ||
respectPrefix | ||
})); | ||
let opts = { | ||
candidate, | ||
context | ||
}; | ||
let result = formatStrings.map((formats)=>(0, _formatVariantSelector.finalizeSelector)(`.${candidate}`, (0, _formatVariantSelector.formatVariantSelector)(formats, opts), opts).replace(`.${candidate}`, "&").replace("{ & }", "").trim()); | ||
if (manualFormatStrings.length > 0) { | ||
result.push((0, _formatVariantSelector.formatVariantSelector)(manualFormatStrings, opts).toString().replace(`.${candidate}`, "&")); | ||
} | ||
return result; | ||
} | ||
}); | ||
} | ||
return result; | ||
}; | ||
} | ||
/** | ||
* Mark as class as retroactively invalid | ||
* | ||
* | ||
* @param {string} candidate | ||
*/ function markInvalidUtilityCandidate(context, candidate) { | ||
if (!context.classCache.has(candidate)) { | ||
return; | ||
} | ||
// Mark this as not being a real utility | ||
context.notClassCache.add(candidate); | ||
// Remove it from any candidate-specific caches | ||
context.classCache.delete(candidate); | ||
context.applyClassCache.delete(candidate); | ||
context.candidateRuleMap.delete(candidate); | ||
context.candidateRuleCache.delete(candidate); | ||
// Ensure the stylesheet gets rebuilt | ||
context.stylesheetCache = null; | ||
} | ||
/** | ||
* Mark as class as retroactively invalid | ||
* | ||
* @param {import('postcss').Node} node | ||
*/ function markInvalidUtilityNode(context, node) { | ||
let candidate = node.raws.tailwind.candidate; | ||
if (!candidate) { | ||
return; | ||
} | ||
for (const entry of context.ruleCache){ | ||
if (entry[1].raws.tailwind.candidate === candidate) { | ||
context.ruleCache.delete(entry); | ||
// context.postCssNodeCache.delete(node) | ||
} | ||
} | ||
markInvalidUtilityCandidate(context, candidate); | ||
} | ||
function createContext(tailwindConfig, changedContent = [], root = _postcss.default.root()) { | ||
var _tailwindConfig_blocklist; | ||
let context = { | ||
disposables: [], | ||
ruleCache: new Set(), | ||
candidateRuleCache: new Map(), | ||
classCache: new Map(), | ||
applyClassCache: new Map(), | ||
notClassCache: new Set(), | ||
// Seed the not class cache with the blocklist (which is only strings) | ||
notClassCache: new Set((_tailwindConfig_blocklist = tailwindConfig.blocklist) !== null && _tailwindConfig_blocklist !== void 0 ? _tailwindConfig_blocklist : []), | ||
postCssNodeCache: new Map(), | ||
@@ -882,3 +1256,6 @@ candidateRuleMap: new Map(), | ||
variantMap: new Map(), | ||
stylesheetCache: null | ||
stylesheetCache: null, | ||
variantOptions: new Map(), | ||
markInvalidUtilityCandidate: (candidate)=>markInvalidUtilityCandidate(context, candidate), | ||
markInvalidUtilityNode: (node)=>markInvalidUtilityNode(context, node) | ||
}; | ||
@@ -889,9 +1266,9 @@ let resolvedPlugins = resolvePlugins(context, root); | ||
} | ||
let contextMap = sharedState.contextMap; | ||
let configContextMap = sharedState.configContextMap; | ||
let contextSourcesMap = sharedState.contextSourcesMap; | ||
let contextMap = _sharedState.contextMap; | ||
let configContextMap = _sharedState.configContextMap; | ||
let contextSourcesMap = _sharedState.contextSourcesMap; | ||
function getContext(root, result, tailwindConfig, userConfigPath, tailwindConfigHash, contextDependencies) { | ||
let sourcePath = result.opts.from; | ||
let isConfigFile = userConfigPath !== null; | ||
sharedState.env.DEBUG && console.log("Source path:", sourcePath); | ||
_sharedState.env.DEBUG && console.log("Source path:", sourcePath); | ||
let existingContext; | ||
@@ -906,7 +1283,7 @@ if (isConfigFile && contextMap.has(sourcePath)) { | ||
} | ||
let cssDidChange = (0, _cacheInvalidationJs).hasContentChanged(sourcePath, root); | ||
let cssDidChange = (0, _cacheInvalidation.hasContentChanged)(sourcePath, root); | ||
// If there's already a context in the cache and we don't need to | ||
// reset the context, return the cached context. | ||
if (existingContext) { | ||
let contextDependenciesChanged = trackModified([ | ||
let [contextDependenciesChanged, mtimesToCommit] = trackModified([ | ||
...contextDependencies | ||
@@ -917,3 +1294,4 @@ ], getFileModifiedMap(existingContext)); | ||
existingContext, | ||
false | ||
false, | ||
mtimesToCommit | ||
]; | ||
@@ -944,5 +1322,8 @@ } | ||
} | ||
sharedState.env.DEBUG && console.log("Setting up new context..."); | ||
_sharedState.env.DEBUG && console.log("Setting up new context..."); | ||
let context = createContext(tailwindConfig, [], root); | ||
trackModified([ | ||
Object.assign(context, { | ||
userConfigPath | ||
}); | ||
let [, mtimesToCommit] = trackModified([ | ||
...contextDependencies | ||
@@ -960,4 +1341,5 @@ ], getFileModifiedMap(context)); | ||
context, | ||
true | ||
true, | ||
mtimesToCommit | ||
]; | ||
} |
@@ -0,1 +1,2 @@ | ||
// @ts-check | ||
"use strict"; | ||
@@ -5,68 +6,24 @@ Object.defineProperty(exports, "__esModule", { | ||
}); | ||
exports.default = setupTrackingContext; | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _path = _interopRequireDefault(require("path")); | ||
var _fastGlob = _interopRequireDefault(require("fast-glob")); | ||
var _quickLru = _interopRequireDefault(require("quick-lru")); | ||
var _normalizePath = _interopRequireDefault(require("normalize-path")); | ||
var _hashConfig = _interopRequireDefault(require("../util/hashConfig")); | ||
var _getModuleDependencies = _interopRequireDefault(require("../lib/getModuleDependencies")); | ||
var _resolveConfig = _interopRequireDefault(require("../public/resolve-config")); | ||
var _resolveConfigPath = _interopRequireDefault(require("../util/resolveConfigPath")); | ||
var _sharedState = require("./sharedState"); | ||
var _setupContextUtils = require("./setupContextUtils"); | ||
var _parseDependency = _interopRequireDefault(require("../util/parseDependency")); | ||
var _validateConfigJs = require("../util/validateConfig.js"); | ||
function setupTrackingContext(configOrPath) { | ||
return ({ tailwindDirectives , registerDependency })=>{ | ||
return (root, result)=>{ | ||
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = getTailwindConfig(configOrPath); | ||
let contextDependencies = new Set(configDependencies); | ||
// If there are no @tailwind or @apply rules, we don't consider this CSS | ||
// file or its 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] = (0, _setupContextUtils).getContext(root, result, tailwindConfig, userConfigPath, tailwindConfigHash, contextDependencies); | ||
let candidateFiles = getCandidateFiles(context, tailwindConfig); | ||
// If there are no @tailwind or @apply 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 = (0, _setupContextUtils).getFileModifiedMap(context); | ||
// Add template paths as postcss dependencies. | ||
for (let fileOrGlob of candidateFiles){ | ||
let dependency = (0, _parseDependency).default(fileOrGlob); | ||
if (dependency) { | ||
registerDependency(dependency); | ||
} | ||
} | ||
for (let changedContent of resolvedChangedContent(context, candidateFiles, fileModifiedMap)){ | ||
context.changedContent.push(changedContent); | ||
} | ||
} | ||
for (let file of configDependencies){ | ||
registerDependency({ | ||
type: "dependency", | ||
file | ||
}); | ||
} | ||
return context; | ||
}; | ||
}; | ||
} | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, // DISABLE_TOUCH = TRUE | ||
// 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 | ||
"default", { | ||
enumerable: true, | ||
get: function() { | ||
return setupTrackingContext; | ||
} | ||
}); | ||
const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); | ||
const _quicklru = /*#__PURE__*/ _interop_require_default(require("@alloc/quick-lru")); | ||
const _hashConfig = /*#__PURE__*/ _interop_require_default(require("../util/hashConfig")); | ||
const _resolveconfig = /*#__PURE__*/ _interop_require_default(require("../public/resolve-config")); | ||
const _resolveConfigPath = /*#__PURE__*/ _interop_require_default(require("../util/resolveConfigPath")); | ||
const _setupContextUtils = require("./setupContextUtils"); | ||
const _parseDependency = /*#__PURE__*/ _interop_require_default(require("../util/parseDependency")); | ||
const _validateConfig = require("../util/validateConfig.js"); | ||
const _content = require("./content.js"); | ||
const _loadconfig = require("../lib/load-config"); | ||
const _getModuleDependencies = /*#__PURE__*/ _interop_require_default(require("./getModuleDependencies")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -76,3 +33,3 @@ default: obj | ||
} | ||
let configPathCache = new _quickLru.default({ | ||
let configPathCache = new _quicklru.default({ | ||
maxSize: 100 | ||
@@ -85,5 +42,3 @@ }); | ||
} | ||
let candidateFiles = tailwindConfig.content.files.filter((item)=>typeof item === "string" | ||
).map((contentPath)=>(0, _normalizePath).default(contentPath) | ||
); | ||
let candidateFiles = (0, _content.parseCandidateFiles)(context, tailwindConfig); | ||
return candidateFilesCache.set(context, candidateFiles).get(context); | ||
@@ -93,7 +48,6 @@ } | ||
function getTailwindConfig(configOrPath) { | ||
let userConfigPath = (0, _resolveConfigPath).default(configOrPath); | ||
let userConfigPath = (0, _resolveConfigPath.default)(configOrPath); | ||
if (userConfigPath !== null) { | ||
let [prevConfig, prevConfigHash, prevDeps, prevModified] = configPathCache.get(userConfigPath) || []; | ||
let newDeps = (0, _getModuleDependencies).default(userConfigPath).map((dep)=>dep.file | ||
); | ||
let newDeps = (0, _getModuleDependencies.default)(userConfigPath); | ||
let modified = false; | ||
@@ -118,8 +72,7 @@ let newModified = new Map(); | ||
// It has changed (based on timestamps), or first run | ||
for (let file1 of newDeps){ | ||
delete require.cache[file1]; | ||
for (let file of newDeps){ | ||
delete require.cache[file]; | ||
} | ||
let newConfig = (0, _resolveConfig).default(require(userConfigPath)); | ||
newConfig = (0, _validateConfigJs).validateConfig(newConfig); | ||
let newHash = (0, _hashConfig).default(newConfig); | ||
let newConfig = (0, _validateConfig.validateConfig)((0, _resolveconfig.default)((0, _loadconfig.loadConfig)(userConfigPath))); | ||
let newHash = (0, _hashConfig.default)(newConfig); | ||
configPathCache.set(userConfigPath, [ | ||
@@ -138,43 +91,82 @@ newConfig, | ||
} | ||
var _configOrPath_config, _ref; | ||
// It's a plain object, not a path | ||
let newConfig = (0, _resolveConfig).default(configOrPath.config === undefined ? configOrPath : configOrPath.config); | ||
newConfig = (0, _validateConfigJs).validateConfig(newConfig); | ||
let newConfig = (0, _resolveconfig.default)((_ref = (_configOrPath_config = configOrPath === null || configOrPath === void 0 ? void 0 : configOrPath.config) !== null && _configOrPath_config !== void 0 ? _configOrPath_config : configOrPath) !== null && _ref !== void 0 ? _ref : {}); | ||
newConfig = (0, _validateConfig.validateConfig)(newConfig); | ||
return [ | ||
newConfig, | ||
null, | ||
(0, _hashConfig).default(newConfig), | ||
(0, _hashConfig.default)(newConfig), | ||
[] | ||
]; | ||
} | ||
function resolvedChangedContent(context, candidateFiles, fileModifiedMap) { | ||
let changedContent = context.tailwindConfig.content.files.filter((item)=>typeof item.raw === "string" | ||
).map(({ raw , extension ="html" })=>({ | ||
content: raw, | ||
extension | ||
}) | ||
); | ||
for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)){ | ||
let content = _fs.default.readFileSync(changedFile, "utf8"); | ||
let extension = _path.default.extname(changedFile).slice(1); | ||
changedContent.push({ | ||
content, | ||
extension | ||
}); | ||
} | ||
return changedContent; | ||
function setupTrackingContext(configOrPath) { | ||
return ({ tailwindDirectives , registerDependency })=>{ | ||
return (root, result)=>{ | ||
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = getTailwindConfig(configOrPath); | ||
let contextDependencies = new Set(configDependencies); | ||
// If there are no @tailwind or @apply rules, we don't consider this CSS | ||
// file or its 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, , mTimesToCommit] = (0, _setupContextUtils.getContext)(root, result, tailwindConfig, userConfigPath, tailwindConfigHash, contextDependencies); | ||
let fileModifiedMap = (0, _setupContextUtils.getFileModifiedMap)(context); | ||
let candidateFiles = getCandidateFiles(context, tailwindConfig); | ||
// If there are no @tailwind or @apply 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 template paths as postcss dependencies. | ||
for (let contentPath of candidateFiles){ | ||
for (let dependency of (0, _parseDependency.default)(contentPath)){ | ||
registerDependency(dependency); | ||
} | ||
} | ||
let [changedContent, contentMTimesToCommit] = (0, _content.resolvedChangedContent)(context, candidateFiles, fileModifiedMap); | ||
for (let content of changedContent){ | ||
context.changedContent.push(content); | ||
} | ||
// Add the mtimes of the content files to the commit list | ||
// We can overwrite the existing values because unconditionally | ||
// This is because: | ||
// 1. Most of the files here won't be in the map yet | ||
// 2. If they are that means it's a context dependency | ||
// and we're reading this after the context. This means | ||
// that the mtime we just read is strictly >= the context | ||
// mtime. Unless the user / os is doing something weird | ||
// in which the mtime would be going backwards. If that | ||
// happens there's already going to be problems. | ||
for (let [path, mtime] of contentMTimesToCommit.entries()){ | ||
mTimesToCommit.set(path, mtime); | ||
} | ||
} | ||
for (let file of configDependencies){ | ||
registerDependency({ | ||
type: "dependency", | ||
file | ||
}); | ||
} | ||
// "commit" the new modified time for all context deps | ||
// We do this here because we want content tracking to | ||
// read the "old" mtime even when it's a context dependency. | ||
for (let [path, mtime] of mTimesToCommit.entries()){ | ||
fileModifiedMap.set(path, mtime); | ||
} | ||
return context; | ||
}; | ||
}; | ||
} | ||
function resolveChangedFiles(candidateFiles, fileModifiedMap) { | ||
let changedFiles = new Set(); | ||
_sharedState.env.DEBUG && console.time("Finding changed files"); | ||
let files = _fastGlob.default.sync(candidateFiles); | ||
for (let file of files){ | ||
let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity; | ||
let modified = _fs.default.statSync(file).mtimeMs; | ||
if (modified > prevModified) { | ||
changedFiles.add(file); | ||
fileModifiedMap.set(file, modified); | ||
} | ||
} | ||
_sharedState.env.DEBUG && console.timeEnd("Finding changed files"); | ||
return changedFiles; | ||
} |
@@ -5,19 +5,47 @@ "use strict"; | ||
}); | ||
exports.resolveDebug = resolveDebug; | ||
exports.NOT_ON_DEMAND = exports.sourceHashMap = exports.contextSourcesMap = exports.configContextMap = exports.contextMap = exports.env = void 0; | ||
const env = { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
env: function() { | ||
return env; | ||
}, | ||
contextMap: function() { | ||
return contextMap; | ||
}, | ||
configContextMap: function() { | ||
return configContextMap; | ||
}, | ||
contextSourcesMap: function() { | ||
return contextSourcesMap; | ||
}, | ||
sourceHashMap: function() { | ||
return sourceHashMap; | ||
}, | ||
NOT_ON_DEMAND: function() { | ||
return NOT_ON_DEMAND; | ||
}, | ||
NONE: function() { | ||
return NONE; | ||
}, | ||
resolveDebug: function() { | ||
return resolveDebug; | ||
} | ||
}); | ||
const env = typeof process !== "undefined" ? { | ||
NODE_ENV: process.env.NODE_ENV, | ||
DEBUG: resolveDebug(process.env.DEBUG) | ||
} : { | ||
NODE_ENV: "production", | ||
DEBUG: false | ||
}; | ||
exports.env = env; | ||
const contextMap = new Map(); | ||
exports.contextMap = contextMap; | ||
const configContextMap = new Map(); | ||
exports.configContextMap = configContextMap; | ||
const contextSourcesMap = new Map(); | ||
exports.contextSourcesMap = contextSourcesMap; | ||
const sourceHashMap = new Map(); | ||
exports.sourceHashMap = sourceHashMap; | ||
const NOT_ON_DEMAND = new String("*"); | ||
exports.NOT_ON_DEMAND = NOT_ON_DEMAND; | ||
const NONE = Symbol("__NONE__"); | ||
function resolveDebug(debug) { | ||
@@ -42,4 +70,3 @@ if (debug === undefined) { | ||
} | ||
let debuggers = debug.split(",").map((d)=>d.split(":")[0] | ||
); | ||
let debuggers = debug.split(",").map((d)=>d.split(":")[0]); | ||
// Ignoring tailwindcss | ||
@@ -46,0 +73,0 @@ if (debuggers.includes("-tailwindcss")) { |
@@ -5,6 +5,11 @@ "use strict"; | ||
}); | ||
exports.default = _default; | ||
var _normalizeScreens = require("../util/normalizeScreens"); | ||
var _buildMediaQuery = _interopRequireDefault(require("../util/buildMediaQuery")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _normalizeScreens = require("../util/normalizeScreens"); | ||
const _buildMediaQuery = /*#__PURE__*/ _interop_require_default(require("../util/buildMediaQuery")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -18,5 +23,4 @@ default: obj | ||
let screen = atRule.params; | ||
let screens = (0, _normalizeScreens).normalizeScreens(theme.screens); | ||
let screenDefinition = screens.find(({ name })=>name === screen | ||
); | ||
let screens = (0, _normalizeScreens.normalizeScreens)(theme.screens); | ||
let screenDefinition = screens.find(({ name })=>name === screen); | ||
if (!screenDefinition) { | ||
@@ -26,5 +30,5 @@ throw atRule.error(`No \`${screen}\` screen found.`); | ||
atRule.name = "media"; | ||
atRule.params = (0, _buildMediaQuery).default(screenDefinition); | ||
atRule.params = (0, _buildMediaQuery.default)(screenDefinition); | ||
}); | ||
}; | ||
} |
@@ -5,9 +5,14 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
var _plugin = require("./plugin"); | ||
var _default = Object.assign(function(opts) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _plugin = require("./plugin"); | ||
const _default = Object.assign(function(opts) { | ||
return { | ||
postcssPlugin: "tailwindcss/nesting", | ||
Once (root, { result }) { | ||
return (0, _plugin).nesting(opts)(root, result); | ||
return (0, _plugin.nesting)(opts)(root, result); | ||
} | ||
@@ -18,2 +23,1 @@ }; | ||
}); | ||
exports.default = _default; |
@@ -5,6 +5,11 @@ "use strict"; | ||
}); | ||
exports.nesting = nesting; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _postcssNested = _interopRequireDefault(require("postcss-nested")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "nesting", { | ||
enumerable: true, | ||
get: function() { | ||
return nesting; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _postcssnested = /*#__PURE__*/ _interop_require_default(require("postcss-nested")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -14,3 +19,3 @@ default: obj | ||
} | ||
function nesting(opts = _postcssNested.default) { | ||
function nesting(opts = _postcssnested.default) { | ||
return (root, result)=>{ | ||
@@ -30,4 +35,4 @@ root.walkAtRules("screen", (rule)=>{ | ||
let plugin = (()=>{ | ||
var ref; | ||
if (typeof opts === "function" || typeof opts === "object" && (opts === null || opts === void 0 ? void 0 : (ref = opts.hasOwnProperty) === null || ref === void 0 ? void 0 : ref.call(opts, "postcssPlugin"))) { | ||
var _opts_hasOwnProperty; | ||
if (typeof opts === "function" || typeof opts === "object" && (opts === null || opts === void 0 ? void 0 : (_opts_hasOwnProperty = opts.hasOwnProperty) === null || _opts_hasOwnProperty === void 0 ? void 0 : _opts_hasOwnProperty.call(opts, "postcssPlugin"))) { | ||
return opts; | ||
@@ -39,7 +44,7 @@ } | ||
if (Object.keys(opts).length <= 0) { | ||
return _postcssNested.default; | ||
return _postcssnested.default; | ||
} | ||
throw new Error("tailwindcss/nesting should be loaded with a nesting plugin."); | ||
})(); | ||
(0, _postcss).default([ | ||
(0, _postcss.default)([ | ||
plugin | ||
@@ -74,4 +79,3 @@ ]).process(root, result.opts).sync(); | ||
if (node.nodes) { | ||
node.nodes.forEach((n)=>markDirty(n) | ||
); | ||
node.nodes.forEach((n)=>markDirty(n)); | ||
} | ||
@@ -78,0 +82,0 @@ // If it's a leaf node mark it as dirty |
@@ -5,22 +5,30 @@ "use strict"; | ||
}); | ||
exports.default = processTailwindFeatures; | ||
var _normalizeTailwindDirectives = _interopRequireDefault(require("./lib/normalizeTailwindDirectives")); | ||
var _expandTailwindAtRules = _interopRequireDefault(require("./lib/expandTailwindAtRules")); | ||
var _expandApplyAtRules = _interopRequireDefault(require("./lib/expandApplyAtRules")); | ||
var _evaluateTailwindFunctions = _interopRequireDefault(require("./lib/evaluateTailwindFunctions")); | ||
var _substituteScreenAtRules = _interopRequireDefault(require("./lib/substituteScreenAtRules")); | ||
var _resolveDefaultsAtRules = _interopRequireDefault(require("./lib/resolveDefaultsAtRules")); | ||
var _collapseAdjacentRules = _interopRequireDefault(require("./lib/collapseAdjacentRules")); | ||
var _collapseDuplicateDeclarations = _interopRequireDefault(require("./lib/collapseDuplicateDeclarations")); | ||
var _partitionApplyAtRules = _interopRequireDefault(require("./lib/partitionApplyAtRules")); | ||
var _detectNesting = _interopRequireDefault(require("./lib/detectNesting")); | ||
var _setupContextUtils = require("./lib/setupContextUtils"); | ||
var _featureFlags = require("./featureFlags"); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return processTailwindFeatures; | ||
} | ||
}); | ||
const _normalizeTailwindDirectives = /*#__PURE__*/ _interop_require_default(require("./lib/normalizeTailwindDirectives")); | ||
const _expandTailwindAtRules = /*#__PURE__*/ _interop_require_default(require("./lib/expandTailwindAtRules")); | ||
const _expandApplyAtRules = /*#__PURE__*/ _interop_require_default(require("./lib/expandApplyAtRules")); | ||
const _evaluateTailwindFunctions = /*#__PURE__*/ _interop_require_default(require("./lib/evaluateTailwindFunctions")); | ||
const _substituteScreenAtRules = /*#__PURE__*/ _interop_require_default(require("./lib/substituteScreenAtRules")); | ||
const _resolveDefaultsAtRules = /*#__PURE__*/ _interop_require_default(require("./lib/resolveDefaultsAtRules")); | ||
const _collapseAdjacentRules = /*#__PURE__*/ _interop_require_default(require("./lib/collapseAdjacentRules")); | ||
const _collapseDuplicateDeclarations = /*#__PURE__*/ _interop_require_default(require("./lib/collapseDuplicateDeclarations")); | ||
const _partitionApplyAtRules = /*#__PURE__*/ _interop_require_default(require("./lib/partitionApplyAtRules")); | ||
const _setupContextUtils = require("./lib/setupContextUtils"); | ||
const _featureFlags = require("./featureFlags"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function processTailwindFeatures(setupContext) { | ||
return function(root, result) { | ||
let { tailwindDirectives , applyDirectives } = (0, _normalizeTailwindDirectives).default(root); | ||
(0, _detectNesting).default()(root, result); | ||
return async function(root, result) { | ||
let { tailwindDirectives , applyDirectives } = (0, _normalizeTailwindDirectives.default)(root); | ||
// Partition apply rules that are found in the css | ||
// itself. | ||
(0, _partitionApplyAtRules).default()(root, result); | ||
(0, _partitionApplyAtRules.default)()(root, result); | ||
let context = setupContext({ | ||
@@ -37,3 +45,3 @@ tailwindDirectives, | ||
createContext (tailwindConfig, changedContent) { | ||
return (0, _setupContextUtils).createContext(tailwindConfig, changedContent, root); | ||
return (0, _setupContextUtils.createContext)(tailwindConfig, changedContent, root); | ||
} | ||
@@ -44,19 +52,14 @@ })(root, result); | ||
} | ||
(0, _featureFlags).issueFlagNotices(context.tailwindConfig); | ||
(0, _expandTailwindAtRules).default(context)(root, result); | ||
(0, _featureFlags.issueFlagNotices)(context.tailwindConfig); | ||
await (0, _expandTailwindAtRules.default)(context)(root, result); | ||
// Partition apply rules that are generated by | ||
// addComponents, addUtilities and so on. | ||
(0, _partitionApplyAtRules).default()(root, result); | ||
(0, _expandApplyAtRules).default(context)(root, result); | ||
(0, _evaluateTailwindFunctions).default(context)(root, result); | ||
(0, _substituteScreenAtRules).default(context)(root, result); | ||
(0, _resolveDefaultsAtRules).default(context)(root, result); | ||
(0, _collapseAdjacentRules).default(context)(root, result); | ||
(0, _collapseDuplicateDeclarations).default(context)(root, result); | ||
(0, _partitionApplyAtRules.default)()(root, result); | ||
(0, _expandApplyAtRules.default)(context)(root, result); | ||
(0, _evaluateTailwindFunctions.default)(context)(root, result); | ||
(0, _substituteScreenAtRules.default)(context)(root, result); | ||
(0, _resolveDefaultsAtRules.default)(context)(root, result); | ||
(0, _collapseAdjacentRules.default)(context)(root, result); | ||
(0, _collapseDuplicateDeclarations.default)(context)(root, result); | ||
}; | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
var _log = _interopRequireDefault(require("../util/log")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("../util/log")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -16,6 +21,6 @@ default: obj | ||
`As of Tailwind CSS ${version}, \`${from}\` has been renamed to \`${to}\`.`, | ||
"Update your configuration file to silence this warning.", | ||
"Update your configuration file to silence this warning." | ||
]); | ||
} | ||
var _default = { | ||
const _default = { | ||
inherit: "inherit", | ||
@@ -36,3 +41,4 @@ current: "currentColor", | ||
800: "#1e293b", | ||
900: "#0f172a" | ||
900: "#0f172a", | ||
950: "#020617" | ||
}, | ||
@@ -49,3 +55,4 @@ gray: { | ||
800: "#1f2937", | ||
900: "#111827" | ||
900: "#111827", | ||
950: "#030712" | ||
}, | ||
@@ -62,3 +69,4 @@ zinc: { | ||
800: "#27272a", | ||
900: "#18181b" | ||
900: "#18181b", | ||
950: "#09090b" | ||
}, | ||
@@ -75,3 +83,4 @@ neutral: { | ||
800: "#262626", | ||
900: "#171717" | ||
900: "#171717", | ||
950: "#0a0a0a" | ||
}, | ||
@@ -88,3 +97,4 @@ stone: { | ||
800: "#292524", | ||
900: "#1c1917" | ||
900: "#1c1917", | ||
950: "#0c0a09" | ||
}, | ||
@@ -101,3 +111,4 @@ red: { | ||
800: "#991b1b", | ||
900: "#7f1d1d" | ||
900: "#7f1d1d", | ||
950: "#450a0a" | ||
}, | ||
@@ -114,3 +125,4 @@ orange: { | ||
800: "#9a3412", | ||
900: "#7c2d12" | ||
900: "#7c2d12", | ||
950: "#431407" | ||
}, | ||
@@ -127,3 +139,4 @@ amber: { | ||
800: "#92400e", | ||
900: "#78350f" | ||
900: "#78350f", | ||
950: "#451a03" | ||
}, | ||
@@ -140,3 +153,4 @@ yellow: { | ||
800: "#854d0e", | ||
900: "#713f12" | ||
900: "#713f12", | ||
950: "#422006" | ||
}, | ||
@@ -153,3 +167,4 @@ lime: { | ||
800: "#3f6212", | ||
900: "#365314" | ||
900: "#365314", | ||
950: "#1a2e05" | ||
}, | ||
@@ -166,3 +181,4 @@ green: { | ||
800: "#166534", | ||
900: "#14532d" | ||
900: "#14532d", | ||
950: "#052e16" | ||
}, | ||
@@ -179,3 +195,4 @@ emerald: { | ||
800: "#065f46", | ||
900: "#064e3b" | ||
900: "#064e3b", | ||
950: "#022c22" | ||
}, | ||
@@ -192,3 +209,4 @@ teal: { | ||
800: "#115e59", | ||
900: "#134e4a" | ||
900: "#134e4a", | ||
950: "#042f2e" | ||
}, | ||
@@ -205,3 +223,4 @@ cyan: { | ||
800: "#155e75", | ||
900: "#164e63" | ||
900: "#164e63", | ||
950: "#083344" | ||
}, | ||
@@ -218,3 +237,4 @@ sky: { | ||
800: "#075985", | ||
900: "#0c4a6e" | ||
900: "#0c4a6e", | ||
950: "#082f49" | ||
}, | ||
@@ -231,3 +251,4 @@ blue: { | ||
800: "#1e40af", | ||
900: "#1e3a8a" | ||
900: "#1e3a8a", | ||
950: "#172554" | ||
}, | ||
@@ -244,3 +265,4 @@ indigo: { | ||
800: "#3730a3", | ||
900: "#312e81" | ||
900: "#312e81", | ||
950: "#1e1b4b" | ||
}, | ||
@@ -257,3 +279,4 @@ violet: { | ||
800: "#5b21b6", | ||
900: "#4c1d95" | ||
900: "#4c1d95", | ||
950: "#2e1065" | ||
}, | ||
@@ -270,3 +293,4 @@ purple: { | ||
800: "#6b21a8", | ||
900: "#581c87" | ||
900: "#581c87", | ||
950: "#3b0764" | ||
}, | ||
@@ -283,3 +307,4 @@ fuchsia: { | ||
800: "#86198f", | ||
900: "#701a75" | ||
900: "#701a75", | ||
950: "#4a044e" | ||
}, | ||
@@ -296,3 +321,4 @@ pink: { | ||
800: "#9d174d", | ||
900: "#831843" | ||
900: "#831843", | ||
950: "#500724" | ||
}, | ||
@@ -309,3 +335,4 @@ rose: { | ||
800: "#9f1239", | ||
900: "#881337" | ||
900: "#881337", | ||
950: "#4c0519" | ||
}, | ||
@@ -353,2 +380,1 @@ get lightBlue () { | ||
}; | ||
exports.default = _default; |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
var _createPlugin = _interopRequireDefault(require("../util/createPlugin")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _createPlugin = /*#__PURE__*/ _interop_require_default(require("../util/createPlugin")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -13,3 +18,2 @@ default: obj | ||
} | ||
var _default = _createPlugin.default; | ||
exports.default = _default; | ||
const _default = _createPlugin.default; |
@@ -5,6 +5,11 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
var _cloneDeep = require("../util/cloneDeep"); | ||
var _defaultConfigStub = _interopRequireDefault(require("../../stubs/defaultConfig.stub")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _cloneDeep = require("../util/cloneDeep"); | ||
const _configfull = /*#__PURE__*/ _interop_require_default(require("../../stubs/config.full")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -14,3 +19,2 @@ default: obj | ||
} | ||
var _default = (0, _cloneDeep).cloneDeep(_defaultConfigStub.default); | ||
exports.default = _default; | ||
const _default = (0, _cloneDeep.cloneDeep)(_configfull.default); |
@@ -5,6 +5,11 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
var _cloneDeep = require("../util/cloneDeep"); | ||
var _defaultConfigStub = _interopRequireDefault(require("../../stubs/defaultConfig.stub")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _cloneDeep = require("../util/cloneDeep"); | ||
const _configfull = /*#__PURE__*/ _interop_require_default(require("../../stubs/config.full")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -14,3 +19,2 @@ default: obj | ||
} | ||
var _default = (0, _cloneDeep).cloneDeep(_defaultConfigStub.default.theme); | ||
exports.default = _default; | ||
const _default = (0, _cloneDeep.cloneDeep)(_configfull.default.theme); |
@@ -5,8 +5,18 @@ "use strict"; | ||
}); | ||
exports.default = resolveConfig; | ||
var _resolveConfig = _interopRequireDefault(require("../util/resolveConfig")); | ||
var _getAllConfigs = _interopRequireDefault(require("../util/getAllConfigs")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return resolveConfig; | ||
} | ||
}); | ||
const _resolveConfig = /*#__PURE__*/ _interop_require_default(require("../util/resolveConfig")); | ||
const _getAllConfigs = /*#__PURE__*/ _interop_require_default(require("../util/getAllConfigs")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function resolveConfig(...configs) { | ||
let [, ...defaultConfigs] = (0, _getAllConfigs).default(configs[0]); | ||
return (0, _resolveConfig).default([ | ||
let [, ...defaultConfigs] = (0, _getAllConfigs.default)(configs[0]); | ||
return (0, _resolveConfig.default)([ | ||
...configs, | ||
@@ -16,6 +26,1 @@ ...defaultConfigs | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.default = bigSign; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return bigSign; | ||
} | ||
}); | ||
function bigSign(bigIntValue) { | ||
return (bigIntValue > 0n) - (bigIntValue < 0n); | ||
} |
@@ -5,3 +5,8 @@ "use strict"; | ||
}); | ||
exports.default = buildMediaQuery; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return buildMediaQuery; | ||
} | ||
}); | ||
function buildMediaQuery(screens) { | ||
@@ -11,3 +16,4 @@ screens = Array.isArray(screens) ? screens : [ | ||
]; | ||
return screens.map((screen1)=>screen1.values.map((screen)=>{ | ||
return screens.map((screen)=>{ | ||
let values = screen.values.map((screen)=>{ | ||
if (screen.raw !== undefined) { | ||
@@ -18,6 +24,7 @@ return screen.raw; | ||
screen.min && `(min-width: ${screen.min})`, | ||
screen.max && `(max-width: ${screen.max})`, | ||
screen.max && `(max-width: ${screen.max})` | ||
].filter(Boolean).join(" and "); | ||
}) | ||
).join(", "); | ||
}); | ||
return screen.not ? `not all and ${values}` : values; | ||
}).join(", "); | ||
} |
@@ -5,7 +5,11 @@ "use strict"; | ||
}); | ||
exports.cloneDeep = cloneDeep; | ||
Object.defineProperty(exports, "cloneDeep", { | ||
enumerable: true, | ||
get: function() { | ||
return cloneDeep; | ||
} | ||
}); | ||
function cloneDeep(value) { | ||
if (Array.isArray(value)) { | ||
return value.map((child)=>cloneDeep(child) | ||
); | ||
return value.map((child)=>cloneDeep(child)); | ||
} | ||
@@ -16,6 +20,5 @@ if (typeof value === "object" && value !== null) { | ||
cloneDeep(v) | ||
] | ||
)); | ||
])); | ||
} | ||
return value; | ||
} |
@@ -1,17 +0,19 @@ | ||
"use strict"; | ||
/** | ||
* @param {import('postcss').Container[]} nodes | ||
* @param {any} source | ||
* @param {any} raws | ||
* @returns {import('postcss').Container[]} | ||
*/ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = cloneNodes; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return cloneNodes; | ||
} | ||
}); | ||
function cloneNodes(nodes, source = undefined, raws = undefined) { | ||
return nodes.map((node)=>{ | ||
let cloned = node.clone(); | ||
if (source !== undefined) { | ||
cloned.source = source; | ||
if ("walk" in cloned) { | ||
cloned.walk((child)=>{ | ||
child.source = source; | ||
}); | ||
} | ||
} | ||
if (raws !== undefined) { | ||
@@ -23,4 +25,31 @@ cloned.raws.tailwind = { | ||
} | ||
if (source !== undefined) { | ||
traverse(cloned, (node)=>{ | ||
var _node_raws_tailwind; | ||
// Do not traverse nodes that have opted | ||
// to preserve their original source | ||
let shouldPreserveSource = ((_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.preserveSource) === true && node.source; | ||
if (shouldPreserveSource) { | ||
return false; | ||
} | ||
// Otherwise we can safely replace the source | ||
// And continue traversing | ||
node.source = source; | ||
}); | ||
} | ||
return cloned; | ||
}); | ||
} | ||
/** | ||
* Traverse a tree of nodes and don't traverse children if the callback | ||
* returns false. Ideally we'd use Container#walk instead of this | ||
* function but it stops traversing siblings too. | ||
* | ||
* @param {import('postcss').Container} node | ||
* @param {(node: import('postcss').Container) => boolean} onNode | ||
*/ function traverse(node, onNode) { | ||
if (onNode(node) !== false) { | ||
var _node_each; | ||
(_node_each = node.each) === null || _node_each === void 0 ? void 0 : _node_each.call(node, (child)=>traverse(child, onNode)); | ||
} | ||
} |
@@ -5,6 +5,18 @@ "use strict"; | ||
}); | ||
exports.parseColor = parseColor; | ||
exports.formatColor = formatColor; | ||
var _colorName = _interopRequireDefault(require("color-name")); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
parseColor: function() { | ||
return parseColor; | ||
}, | ||
formatColor: function() { | ||
return formatColor; | ||
} | ||
}); | ||
const _colorNames = /*#__PURE__*/ _interop_require_default(require("./colorNames")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -19,7 +31,7 @@ default: obj | ||
let ALPHA_SEP = /\s*[,/]\s*/; | ||
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/; | ||
let RGB = new RegExp(`^(rgb)a?\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`); | ||
let HSL = new RegExp(`^(hsl)a?\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`); | ||
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/; | ||
let RGB = new RegExp(`^(rgba?)\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`); | ||
let HSL = new RegExp(`^(hsla?)\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`); | ||
function parseColor(value, { loose =false } = {}) { | ||
var ref, ref1; | ||
var _match_, _match__toString; | ||
if (typeof value !== "string") { | ||
@@ -40,7 +52,6 @@ return null; | ||
} | ||
if (value in _colorName.default) { | ||
if (value in _colorNames.default) { | ||
return { | ||
mode: "rgb", | ||
color: _colorName.default[value].map((v)=>v.toString() | ||
) | ||
color: _colorNames.default[value].map((v)=>v.toString()) | ||
}; | ||
@@ -57,4 +68,3 @@ } | ||
a ? a + a : "" | ||
].join("") | ||
).match(HEX); | ||
].join("")).match(HEX); | ||
if (hex !== null) { | ||
@@ -67,9 +77,8 @@ return { | ||
parseInt(hex[3], 16) | ||
].map((v)=>v.toString() | ||
), | ||
].map((v)=>v.toString()), | ||
alpha: hex[4] ? (parseInt(hex[4], 16) / 255).toString() : undefined | ||
}; | ||
} | ||
var ref2; | ||
let match = (ref2 = value.match(RGB)) !== null && ref2 !== void 0 ? ref2 : value.match(HSL); | ||
var _value_match; | ||
let match = (_value_match = value.match(RGB)) !== null && _value_match !== void 0 ? _value_match : value.match(HSL); | ||
if (match === null) { | ||
@@ -82,9 +91,18 @@ return null; | ||
match[4] | ||
].filter(Boolean).map((v)=>v.toString() | ||
); | ||
].filter(Boolean).map((v)=>v.toString()); | ||
// rgba(var(--my-color), 0.1) | ||
// hsla(var(--my-color), 0.1) | ||
if (color.length === 2 && color[0].startsWith("var(")) { | ||
return { | ||
mode: match[1], | ||
color: [ | ||
color[0] | ||
], | ||
alpha: color[1] | ||
}; | ||
} | ||
if (!loose && color.length !== 3) { | ||
return null; | ||
} | ||
if (color.length < 3 && !color.some((part)=>/^var\(.*?\)$/.test(part) | ||
)) { | ||
if (color.length < 3 && !color.some((part)=>/^var\(.*?\)$/.test(part))) { | ||
return null; | ||
@@ -95,3 +113,3 @@ } | ||
color, | ||
alpha: (ref = match[5]) === null || ref === void 0 ? void 0 : (ref1 = ref.toString) === null || ref1 === void 0 ? void 0 : ref1.call(ref) | ||
alpha: (_match_ = match[5]) === null || _match_ === void 0 ? void 0 : (_match__toString = _match_.toString) === null || _match__toString === void 0 ? void 0 : _match__toString.call(_match_) | ||
}; | ||
@@ -101,3 +119,6 @@ } | ||
let hasAlpha = alpha !== undefined; | ||
if (mode === "rgba" || mode === "hsla") { | ||
return `${mode}(${color.join(", ")}${hasAlpha ? `, ${alpha}` : ""})`; | ||
} | ||
return `${mode}(${color.join(" ")}${hasAlpha ? ` / ${alpha}` : ""})`; | ||
} |
@@ -5,3 +5,8 @@ "use strict"; | ||
}); | ||
exports.default = _default; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
function _default(pluginConfig, plugins) { | ||
@@ -16,5 +21,5 @@ if (pluginConfig === undefined) { | ||
return pluginConfig[pluginName] !== false; | ||
}))), | ||
}))) | ||
]; | ||
return pluginNames; | ||
} |
@@ -5,3 +5,8 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
function createPlugin(plugin, config) { | ||
@@ -13,4 +18,3 @@ return { | ||
} | ||
createPlugin.withOptions = function(pluginFunction, configFunction = ()=>({}) | ||
) { | ||
createPlugin.withOptions = function(pluginFunction, configFunction = ()=>({})) { | ||
const optionsFunction = function(options) { | ||
@@ -30,3 +34,2 @@ return { | ||
}; | ||
var _default = createPlugin; | ||
exports.default = _default; | ||
const _default = createPlugin; |
@@ -5,4 +5,14 @@ "use strict"; | ||
}); | ||
exports.default = createUtilityPlugin; | ||
var _transformThemeValue = _interopRequireDefault(require("./transformThemeValue")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return createUtilityPlugin; | ||
} | ||
}); | ||
const _transformThemeValue = /*#__PURE__*/ _interop_require_default(require("./transformThemeValue")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function createUtilityPlugin(themeKey, utilityVariations = [ | ||
@@ -16,3 +26,3 @@ [ | ||
], { filterDefault =false , ...options } = {}) { | ||
let transformValue = (0, _transformThemeValue).default(themeKey); | ||
let transformValue = (0, _transformThemeValue.default)(themeKey); | ||
return function({ matchUtilities , theme }) { | ||
@@ -23,5 +33,5 @@ for (let utilityVariation of utilityVariations){ | ||
]; | ||
var ref; | ||
matchUtilities(group.reduce((obj1, [classPrefix, properties])=>{ | ||
return Object.assign(obj1, { | ||
var _theme; | ||
matchUtilities(group.reduce((obj, [classPrefix, properties])=>{ | ||
return Object.assign(obj, { | ||
[classPrefix]: (value)=>{ | ||
@@ -42,4 +52,3 @@ return properties.reduce((obj, name)=>{ | ||
...options, | ||
values: filterDefault ? Object.fromEntries(Object.entries((ref = theme(themeKey)) !== null && ref !== void 0 ? ref : {}).filter(([modifier])=>modifier !== "DEFAULT" | ||
)) : theme(themeKey) | ||
values: filterDefault ? Object.fromEntries(Object.entries((_theme = theme(themeKey)) !== null && _theme !== void 0 ? _theme : {}).filter(([modifier])=>modifier !== "DEFAULT")) : theme(themeKey) | ||
}); | ||
@@ -49,6 +58,1 @@ } | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,19 +5,61 @@ "use strict"; | ||
}); | ||
exports.normalize = normalize; | ||
exports.url = url; | ||
exports.number = number; | ||
exports.percentage = percentage; | ||
exports.length = length; | ||
exports.lineWidth = lineWidth; | ||
exports.shadow = shadow; | ||
exports.color = color; | ||
exports.image = image; | ||
exports.gradient = gradient; | ||
exports.position = position; | ||
exports.familyName = familyName; | ||
exports.genericName = genericName; | ||
exports.absoluteSize = absoluteSize; | ||
exports.relativeSize = relativeSize; | ||
var _color = require("./color"); | ||
var _parseBoxShadowValue = require("./parseBoxShadowValue"); | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
normalize: function() { | ||
return normalize; | ||
}, | ||
normalizeAttributeSelectors: function() { | ||
return normalizeAttributeSelectors; | ||
}, | ||
url: function() { | ||
return url; | ||
}, | ||
number: function() { | ||
return number; | ||
}, | ||
percentage: function() { | ||
return percentage; | ||
}, | ||
length: function() { | ||
return length; | ||
}, | ||
lineWidth: function() { | ||
return lineWidth; | ||
}, | ||
shadow: function() { | ||
return shadow; | ||
}, | ||
color: function() { | ||
return color; | ||
}, | ||
image: function() { | ||
return image; | ||
}, | ||
gradient: function() { | ||
return gradient; | ||
}, | ||
position: function() { | ||
return position; | ||
}, | ||
familyName: function() { | ||
return familyName; | ||
}, | ||
genericName: function() { | ||
return genericName; | ||
}, | ||
absoluteSize: function() { | ||
return absoluteSize; | ||
}, | ||
relativeSize: function() { | ||
return relativeSize; | ||
} | ||
}); | ||
const _color = require("./color"); | ||
const _parseBoxShadowValue = require("./parseBoxShadowValue"); | ||
const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly"); | ||
let cssFunctions = [ | ||
@@ -30,7 +72,37 @@ "min", | ||
// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types | ||
let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. | ||
; | ||
let UNDERSCORE = /_(?![^(]*\))/g // Underscore separator that is not located between brackets. E.g.: `rgba(255,_255,_255)_black` these don't count. | ||
; | ||
function normalize(value, isRoot = true) { | ||
function isCSSFunction(value) { | ||
return cssFunctions.some((fn)=>new RegExp(`^${fn}\\(.*\\)`).test(value)); | ||
} | ||
// These properties accept a `<dashed-ident>` as one of the values. This means that you can use them | ||
// as: `timeline-scope: --tl;` | ||
// | ||
// Without the `var(--tl)`, in these cases we don't want to normalize the value, and you should add | ||
// the `var()` yourself. | ||
// | ||
// More info: | ||
// - https://drafts.csswg.org/scroll-animations/#propdef-timeline-scope | ||
// - https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope#dashed-ident | ||
// - https://www.w3.org/TR/css-anchor-position-1 | ||
// | ||
const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([ | ||
// Concrete properties | ||
"scroll-timeline-name", | ||
"timeline-scope", | ||
"view-timeline-name", | ||
"font-palette", | ||
"anchor-name", | ||
"anchor-scope", | ||
"position-anchor", | ||
"position-try-options", | ||
// Shorthand properties | ||
"scroll-timeline", | ||
"animation-timeline", | ||
"view-timeline", | ||
"position-try" | ||
]); | ||
function normalize(value, context = null, isRoot = true) { | ||
let isVarException = context && AUTO_VAR_INJECTION_EXCEPTIONS.has(context.property); | ||
if (value.startsWith("--") && !isVarException) { | ||
return `var(${value})`; | ||
} | ||
// Keep raw strings if it starts with `url(` | ||
@@ -42,8 +114,7 @@ if (value.includes("url(")) { | ||
} | ||
return normalize(part, false); | ||
return normalize(part, context, false); | ||
}).join(""); | ||
} | ||
// Convert `_` to ` `, except for escaped underscores `\_` | ||
value = value.replace(/([^\\])_+/g, (fullMatch, characterBefore)=>characterBefore + " ".repeat(fullMatch.length - 1) | ||
).replace(/^_/g, " ").replace(/\\_/g, "_"); | ||
value = value.replace(/([^\\])_+/g, (fullMatch, characterBefore)=>characterBefore + " ".repeat(fullMatch.length - 1)).replace(/^_/g, " ").replace(/\\_/g, "_"); | ||
// Remove leftover whitespace | ||
@@ -53,10 +124,129 @@ if (isRoot) { | ||
} | ||
// Add spaces around operators inside calc() that do not follow an operator | ||
// or '('. | ||
value = value.replace(/calc\(.+\)/g, (match)=>{ | ||
return match.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, "$1 $2 "); | ||
value = normalizeMathOperatorSpacing(value); | ||
return value; | ||
} | ||
function normalizeAttributeSelectors(value) { | ||
// Wrap values in attribute selectors with quotes | ||
if (value.includes("=")) { | ||
value = value.replace(/(=.*)/g, (_fullMatch, match)=>{ | ||
if (match[1] === "'" || match[1] === '"') { | ||
return match; | ||
} | ||
// Handle regex flags on unescaped values | ||
if (match.length > 2) { | ||
let trailingCharacter = match[match.length - 1]; | ||
if (match[match.length - 2] === " " && (trailingCharacter === "i" || trailingCharacter === "I" || trailingCharacter === "s" || trailingCharacter === "S")) { | ||
return `="${match.slice(1, -2)}" ${match[match.length - 1]}`; | ||
} | ||
} | ||
return `="${match.slice(1)}"`; | ||
}); | ||
} | ||
return value; | ||
} | ||
/** | ||
* Add spaces around operators inside math functions | ||
* like calc() that do not follow an operator, '(', or `,`. | ||
* | ||
* @param {string} value | ||
* @returns {string} | ||
*/ function normalizeMathOperatorSpacing(value) { | ||
let preventFormattingInFunctions = [ | ||
"theme" | ||
]; | ||
let preventFormattingKeywords = [ | ||
"min-content", | ||
"max-content", | ||
"fit-content", | ||
// Env | ||
"safe-area-inset-top", | ||
"safe-area-inset-right", | ||
"safe-area-inset-bottom", | ||
"safe-area-inset-left", | ||
"titlebar-area-x", | ||
"titlebar-area-y", | ||
"titlebar-area-width", | ||
"titlebar-area-height", | ||
"keyboard-inset-top", | ||
"keyboard-inset-right", | ||
"keyboard-inset-bottom", | ||
"keyboard-inset-left", | ||
"keyboard-inset-width", | ||
"keyboard-inset-height", | ||
"radial-gradient", | ||
"linear-gradient", | ||
"conic-gradient", | ||
"repeating-radial-gradient", | ||
"repeating-linear-gradient", | ||
"repeating-conic-gradient" | ||
]; | ||
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match)=>{ | ||
let result = ""; | ||
function lastChar() { | ||
let char = result.trimEnd(); | ||
return char[char.length - 1]; | ||
} | ||
for(let i = 0; i < match.length; i++){ | ||
function peek(word) { | ||
return word.split("").every((char, j)=>match[i + j] === char); | ||
} | ||
function consumeUntil(chars) { | ||
let minIndex = Infinity; | ||
for (let char of chars){ | ||
let index = match.indexOf(char, i); | ||
if (index !== -1 && index < minIndex) { | ||
minIndex = index; | ||
} | ||
} | ||
let result = match.slice(i, minIndex); | ||
i += result.length - 1; | ||
return result; | ||
} | ||
let char = match[i]; | ||
// Handle `var(--variable)` | ||
if (peek("var")) { | ||
// When we consume until `)`, then we are dealing with this scenario: | ||
// `var(--example)` | ||
// | ||
// When we consume until `,`, then we are dealing with this scenario: | ||
// `var(--example, 1rem)` | ||
// | ||
// In this case we do want to "format", the default value as well | ||
result += consumeUntil([ | ||
")", | ||
"," | ||
]); | ||
} else if (preventFormattingKeywords.some((keyword)=>peek(keyword))) { | ||
let keyword = preventFormattingKeywords.find((keyword)=>peek(keyword)); | ||
result += keyword; | ||
i += keyword.length - 1; | ||
} else if (preventFormattingInFunctions.some((fn)=>peek(fn))) { | ||
result += consumeUntil([ | ||
")" | ||
]); | ||
} else if (peek("[")) { | ||
result += consumeUntil([ | ||
"]" | ||
]); | ||
} else if ([ | ||
"+", | ||
"-", | ||
"*", | ||
"/" | ||
].includes(char) && ![ | ||
"(", | ||
"+", | ||
"-", | ||
"*", | ||
"/", | ||
"," | ||
].includes(lastChar())) { | ||
result += ` ${char} `; | ||
} else { | ||
result += char; | ||
} | ||
} | ||
// Simplify multiple spaces | ||
return result.replace(/\s+/g, " "); | ||
}); | ||
// Add spaces around some operators not inside calc() that do not follow an operator | ||
// or '('. | ||
return value.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([\/])/g, "$1 $2 "); | ||
} | ||
@@ -67,11 +257,10 @@ function url(value) { | ||
function number(value) { | ||
return !isNaN(Number(value)) || cssFunctions.some((fn)=>new RegExp(`^${fn}\\(.+?`).test(value) | ||
); | ||
return !isNaN(Number(value)) || isCSSFunction(value); | ||
} | ||
function percentage(value) { | ||
return value.split(UNDERSCORE).every((part)=>{ | ||
return /%$/g.test(part) || cssFunctions.some((fn)=>new RegExp(`^${fn}\\(.+?%`).test(part) | ||
); | ||
}); | ||
return value.endsWith("%") && number(value.slice(0, -1)) || isCSSFunction(value); | ||
} | ||
// Please refer to MDN when updating this list: | ||
// https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units | ||
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units | ||
let lengthUnits = [ | ||
@@ -90,13 +279,25 @@ "cm", | ||
"lh", | ||
"rlh", | ||
"vw", | ||
"vh", | ||
"vmin", | ||
"vmax", | ||
"vmax", | ||
"vb", | ||
"vi", | ||
"svw", | ||
"svh", | ||
"lvw", | ||
"lvh", | ||
"dvw", | ||
"dvh", | ||
"cqw", | ||
"cqh", | ||
"cqi", | ||
"cqb", | ||
"cqmin", | ||
"cqmax" | ||
]; | ||
let lengthUnitsPattern = `(?:${lengthUnits.join("|")})`; | ||
function length(value) { | ||
return value.split(UNDERSCORE).every((part)=>{ | ||
return part === "0" || new RegExp(`${lengthUnitsPattern}$`).test(part) || cssFunctions.some((fn)=>new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part) | ||
); | ||
}); | ||
return value === "0" || new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) || isCSSFunction(value); | ||
} | ||
@@ -112,3 +313,3 @@ let lineWidths = new Set([ | ||
function shadow(value) { | ||
let parsedShadows = (0, _parseBoxShadowValue).parseBoxShadowValue(normalize(value)); | ||
let parsedShadows = (0, _parseBoxShadowValue.parseBoxShadowValue)(normalize(value)); | ||
for (let parsedShadow of parsedShadows){ | ||
@@ -123,6 +324,6 @@ if (!parsedShadow.valid) { | ||
let colors = 0; | ||
let result = value.split(UNDERSCORE).every((part)=>{ | ||
let result = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(value, "_").every((part)=>{ | ||
part = normalize(part); | ||
if (part.startsWith("var(")) return true; | ||
if ((0, _color).parseColor(part, { | ||
if ((0, _color.parseColor)(part, { | ||
loose: true | ||
@@ -137,3 +338,3 @@ }) !== null) return colors++, true; | ||
let images = 0; | ||
let result = value.split(COMMA).every((part)=>{ | ||
let result = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(value, ",").every((part)=>{ | ||
part = normalize(part); | ||
@@ -146,4 +347,3 @@ if (part.startsWith("var(")) return true; | ||
"image-set(" | ||
].some((fn)=>part.startsWith(fn) | ||
)) { | ||
].some((fn)=>part.startsWith(fn))) { | ||
images++; | ||
@@ -158,7 +358,8 @@ return true; | ||
let gradientTypes = new Set([ | ||
"conic-gradient", | ||
"linear-gradient", | ||
"radial-gradient", | ||
"repeating-conic-gradient", | ||
"repeating-linear-gradient", | ||
"repeating-radial-gradient", | ||
"conic-gradient", | ||
"repeating-radial-gradient" | ||
]); | ||
@@ -183,3 +384,3 @@ function gradient(value) { | ||
let positions = 0; | ||
let result = value.split(UNDERSCORE).every((part)=>{ | ||
let result = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(value, "_").every((part)=>{ | ||
part = normalize(part); | ||
@@ -198,3 +399,3 @@ if (part.startsWith("var(")) return true; | ||
let fonts = 0; | ||
let result = value.split(COMMA).every((part)=>{ | ||
let result = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(value, ",").every((part)=>{ | ||
part = normalize(part); | ||
@@ -231,3 +432,3 @@ if (part.startsWith("var(")) return true; | ||
"emoji", | ||
"fangsong", | ||
"fangsong" | ||
]); | ||
@@ -244,4 +445,4 @@ function genericName(value) { | ||
"x-large", | ||
"x-large", | ||
"xxx-large", | ||
"xx-large", | ||
"xxx-large" | ||
]); | ||
@@ -248,0 +449,0 @@ function absoluteSize(value) { |
@@ -5,15 +5,20 @@ "use strict"; | ||
}); | ||
exports.defaults = defaults; | ||
Object.defineProperty(exports, "defaults", { | ||
enumerable: true, | ||
get: function() { | ||
return defaults; | ||
} | ||
}); | ||
function defaults(target, ...sources) { | ||
for (let source of sources){ | ||
for(let k in source){ | ||
var ref; | ||
if (!(target === null || target === void 0 ? void 0 : (ref = target.hasOwnProperty) === null || ref === void 0 ? void 0 : ref.call(target, k))) { | ||
var _target_hasOwnProperty; | ||
if (!(target === null || target === void 0 ? void 0 : (_target_hasOwnProperty = target.hasOwnProperty) === null || _target_hasOwnProperty === void 0 ? void 0 : _target_hasOwnProperty.call(target, k))) { | ||
target[k] = source[k]; | ||
} | ||
} | ||
for (let k1 of Object.getOwnPropertySymbols(source)){ | ||
var ref1; | ||
if (!(target === null || target === void 0 ? void 0 : (ref1 = target.hasOwnProperty) === null || ref1 === void 0 ? void 0 : ref1.call(target, k1))) { | ||
target[k1] = source[k1]; | ||
for (let k of Object.getOwnPropertySymbols(source)){ | ||
var _target_hasOwnProperty1; | ||
if (!(target === null || target === void 0 ? void 0 : (_target_hasOwnProperty1 = target.hasOwnProperty) === null || _target_hasOwnProperty1 === void 0 ? void 0 : _target_hasOwnProperty1.call(target, k))) { | ||
target[k] = source[k]; | ||
} | ||
@@ -20,0 +25,0 @@ } |
@@ -5,13 +5,11 @@ "use strict"; | ||
}); | ||
exports.default = escapeClassName; | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _escapeCommas = _interopRequireDefault(require("./escapeCommas")); | ||
function escapeClassName(className) { | ||
var ref; | ||
let node = _postcssSelectorParser.default.className(); | ||
node.value = className; | ||
var ref1; | ||
return (0, _escapeCommas).default((ref1 = node === null || node === void 0 ? void 0 : (ref = node.raws) === null || ref === void 0 ? void 0 : ref.value) !== null && ref1 !== void 0 ? ref1 : node.value); | ||
} | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return escapeClassName; | ||
} | ||
}); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
const _escapeCommas = /*#__PURE__*/ _interop_require_default(require("./escapeCommas")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -21,1 +19,8 @@ default: obj | ||
} | ||
function escapeClassName(className) { | ||
var _node_raws; | ||
let node = _postcssselectorparser.default.className(); | ||
node.value = className; | ||
var _node_raws_value; | ||
return (0, _escapeCommas.default)((_node_raws_value = node === null || node === void 0 ? void 0 : (_node_raws = node.raws) === null || _node_raws === void 0 ? void 0 : _node_raws.value) !== null && _node_raws_value !== void 0 ? _node_raws_value : node.value); | ||
} |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.default = escapeCommas; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return escapeCommas; | ||
} | ||
}); | ||
function escapeCommas(className) { | ||
return className.replace(/\\,/g, "\\2c "); | ||
} |
@@ -5,14 +5,15 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const flattenColorPalette = (colors)=>Object.assign({}, ...Object.entries(colors !== null && colors !== void 0 ? colors : {}).flatMap(([color, values])=>typeof values == "object" ? Object.entries(flattenColorPalette(values)).map(([number, hex])=>({ | ||
[color + (number === "DEFAULT" ? "" : `-${number}`)]: hex | ||
}) | ||
) : [ | ||
})) : [ | ||
{ | ||
[`${color}`]: values | ||
} | ||
] | ||
)) | ||
; | ||
var _default = flattenColorPalette; | ||
exports.default = _default; | ||
])); | ||
const _default = flattenColorPalette; |
@@ -5,10 +5,29 @@ "use strict"; | ||
}); | ||
exports.formatVariantSelector = formatVariantSelector; | ||
exports.finalizeSelector = finalizeSelector; | ||
exports.selectorFunctions = void 0; | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _unesc = _interopRequireDefault(require("postcss-selector-parser/dist/util/unesc")); | ||
var _escapeClassName = _interopRequireDefault(require("../util/escapeClassName")); | ||
var _prefixSelector = _interopRequireDefault(require("../util/prefixSelector")); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
formatVariantSelector: function() { | ||
return formatVariantSelector; | ||
}, | ||
eliminateIrrelevantSelectors: function() { | ||
return eliminateIrrelevantSelectors; | ||
}, | ||
finalizeSelector: function() { | ||
return finalizeSelector; | ||
}, | ||
handleMergePseudo: function() { | ||
return handleMergePseudo; | ||
} | ||
}); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
const _unesc = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser/dist/util/unesc")); | ||
const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName")); | ||
const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector")); | ||
const _pseudoElements = require("./pseudoElements"); | ||
const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -18,30 +37,101 @@ default: obj | ||
} | ||
let MERGE = ":merge"; | ||
let PARENT = "&"; | ||
let selectorFunctions = new Set([ | ||
MERGE | ||
]); | ||
exports.selectorFunctions = selectorFunctions; | ||
function formatVariantSelector(current, ...others) { | ||
for (let other of others){ | ||
let incomingValue = resolveFunctionArgument(other, MERGE); | ||
if (incomingValue !== null) { | ||
let existingValue = resolveFunctionArgument(current, MERGE, incomingValue); | ||
if (existingValue !== null) { | ||
let existingTarget = `${MERGE}(${incomingValue})`; | ||
let splitIdx = other.indexOf(existingTarget); | ||
let addition = other.slice(splitIdx + existingTarget.length).split(" ")[0]; | ||
current = current.replace(existingTarget, existingTarget + addition); | ||
continue; | ||
} | ||
/** @typedef {import('postcss-selector-parser').Root} Root */ /** @typedef {import('postcss-selector-parser').Selector} Selector */ /** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */ /** @typedef {import('postcss-selector-parser').Node} Node */ /** @typedef {{format: string, respectPrefix: boolean}[]} RawFormats */ /** @typedef {import('postcss-selector-parser').Root} ParsedFormats */ /** @typedef {RawFormats | ParsedFormats} AcceptedFormats */ let MERGE = ":merge"; | ||
function formatVariantSelector(formats, { context , candidate }) { | ||
var _context_tailwindConfig_prefix; | ||
let prefix = (_context_tailwindConfig_prefix = context === null || context === void 0 ? void 0 : context.tailwindConfig.prefix) !== null && _context_tailwindConfig_prefix !== void 0 ? _context_tailwindConfig_prefix : ""; | ||
// Parse the format selector into an AST | ||
let parsedFormats = formats.map((format)=>{ | ||
let ast = (0, _postcssselectorparser.default)().astSync(format.format); | ||
return { | ||
...format, | ||
ast: format.respectPrefix ? (0, _prefixSelector.default)(prefix, ast) : ast | ||
}; | ||
}); | ||
// We start with the candidate selector | ||
let formatAst = _postcssselectorparser.default.root({ | ||
nodes: [ | ||
_postcssselectorparser.default.selector({ | ||
nodes: [ | ||
_postcssselectorparser.default.className({ | ||
value: (0, _escapeClassName.default)(candidate) | ||
}) | ||
] | ||
}) | ||
] | ||
}); | ||
// And iteratively merge each format selector into the candidate selector | ||
for (let { ast } of parsedFormats){ | ||
[formatAst, ast] = handleMergePseudo(formatAst, ast); | ||
// 2. Merge the format selector into the current selector AST | ||
ast.walkNesting((nesting)=>nesting.replaceWith(...formatAst.nodes[0].nodes)); | ||
// 3. Keep going! | ||
formatAst = ast; | ||
} | ||
return formatAst; | ||
} | ||
/** | ||
* Given any node in a selector this gets the "simple" selector it's a part of | ||
* A simple selector is just a list of nodes without any combinators | ||
* Technically :is(), :not(), :has(), etc… can have combinators but those are nested | ||
* inside the relevant node and won't be picked up so they're fine to ignore | ||
* | ||
* @param {Node} node | ||
* @returns {Node[]} | ||
**/ function simpleSelectorForNode(node) { | ||
/** @type {Node[]} */ let nodes = []; | ||
// Walk backwards until we hit a combinator node (or the start) | ||
while(node.prev() && node.prev().type !== "combinator"){ | ||
node = node.prev(); | ||
} | ||
// Now record all non-combinator nodes until we hit one (or the end) | ||
while(node && node.type !== "combinator"){ | ||
nodes.push(node); | ||
node = node.next(); | ||
} | ||
return nodes; | ||
} | ||
/** | ||
* Resorts the nodes in a selector to ensure they're in the correct order | ||
* Tags go before classes, and pseudo classes go after classes | ||
* | ||
* @param {Selector} sel | ||
* @returns {Selector} | ||
**/ function resortSelector(sel) { | ||
sel.sort((a, b)=>{ | ||
if (a.type === "tag" && b.type === "class") { | ||
return -1; | ||
} else if (a.type === "class" && b.type === "tag") { | ||
return 1; | ||
} else if (a.type === "class" && b.type === "pseudo" && b.value.startsWith("::")) { | ||
return -1; | ||
} else if (a.type === "pseudo" && a.value.startsWith("::") && b.type === "class") { | ||
return 1; | ||
} | ||
current = other.replace(PARENT, current); | ||
return sel.index(a) - sel.index(b); | ||
}); | ||
return sel; | ||
} | ||
function eliminateIrrelevantSelectors(sel, base) { | ||
let hasClassesMatchingCandidate = false; | ||
sel.walk((child)=>{ | ||
if (child.type === "class" && child.value === base) { | ||
hasClassesMatchingCandidate = true; | ||
return false // Stop walking | ||
; | ||
} | ||
}); | ||
if (!hasClassesMatchingCandidate) { | ||
sel.remove(); | ||
} | ||
return current; | ||
// We do NOT recursively eliminate sub selectors that don't have the base class | ||
// as this is NOT a safe operation. For example, if we have: | ||
// `.space-x-2 > :not([hidden]) ~ :not([hidden])` | ||
// We cannot remove the [hidden] from the :not() because it would change the | ||
// meaning of the selector. | ||
// TODO: Can we do this for :matches, :is, and :where? | ||
} | ||
function finalizeSelector(format, { selector: selector1 , candidate , context }) { | ||
var ref, ref1; | ||
let ast = (0, _postcssSelectorParser).default().astSync(selector1); | ||
var ref2; | ||
let separator = (ref2 = context === null || context === void 0 ? void 0 : (ref = context.tailwindConfig) === null || ref === void 0 ? void 0 : ref.separator) !== null && ref2 !== void 0 ? ref2 : ":"; | ||
function finalizeSelector(current, formats, { context , candidate , base }) { | ||
var _context_tailwindConfig; | ||
var _context_tailwindConfig_separator; | ||
let separator = (_context_tailwindConfig_separator = context === null || context === void 0 ? void 0 : (_context_tailwindConfig = context.tailwindConfig) === null || _context_tailwindConfig === void 0 ? void 0 : _context_tailwindConfig.separator) !== null && _context_tailwindConfig_separator !== void 0 ? _context_tailwindConfig_separator : ":"; | ||
// Split by the separator, but ignore the separator inside square brackets: | ||
@@ -54,19 +144,5 @@ // | ||
// | ||
let splitter = new RegExp(`\\${separator}(?![^[]*\\])`); | ||
let base = candidate.split(splitter).pop(); | ||
if (context === null || context === void 0 ? void 0 : (ref1 = context.tailwindConfig) === null || ref1 === void 0 ? void 0 : ref1.prefix) { | ||
format = (0, _prefixSelector).default(context.tailwindConfig.prefix, format); | ||
} | ||
format = format.replace(PARENT, `.${(0, _escapeClassName).default(candidate)}`); | ||
let formatAst = (0, _postcssSelectorParser).default().astSync(format); | ||
// Remove extraneous selectors that do not include the base class/candidate being matched against | ||
// For example if we have a utility defined `.a, .b { color: red}` | ||
// And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b` | ||
ast.each((node)=>{ | ||
let hasClassesMatchingCandidate = node.some((n)=>n.type === "class" && n.value === base | ||
); | ||
if (!hasClassesMatchingCandidate) { | ||
node.remove(); | ||
} | ||
}); | ||
base = base !== null && base !== void 0 ? base : (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(candidate, separator).pop(); | ||
// Parse the selector into an AST | ||
let selector = (0, _postcssselectorparser.default)().astSync(current); | ||
// Normalize escaped classes, e.g.: | ||
@@ -83,118 +159,116 @@ // | ||
// | ||
ast.walkClasses((node)=>{ | ||
selector.walkClasses((node)=>{ | ||
if (node.raws && node.value.includes(base)) { | ||
node.raws.value = (0, _escapeClassName).default((0, _unesc).default(node.raws.value)); | ||
node.raws.value = (0, _escapeClassName.default)((0, _unesc.default)(node.raws.value)); | ||
} | ||
}); | ||
// Remove extraneous selectors that do not include the base candidate | ||
selector.each((sel)=>eliminateIrrelevantSelectors(sel, base)); | ||
// If ffter eliminating irrelevant selectors, we end up with nothing | ||
// Then the whole "rule" this is associated with does not need to exist | ||
// We use `null` as a marker value for that case | ||
if (selector.length === 0) { | ||
return null; | ||
} | ||
// If there are no formats that means there were no variants added to the candidate | ||
// so we can just return the selector as-is | ||
let formatAst = Array.isArray(formats) ? formatVariantSelector(formats, { | ||
context, | ||
candidate | ||
}) : formats; | ||
if (formatAst === null) { | ||
return selector.toString(); | ||
} | ||
let simpleStart = _postcssselectorparser.default.comment({ | ||
value: "/*__simple__*/" | ||
}); | ||
let simpleEnd = _postcssselectorparser.default.comment({ | ||
value: "/*__simple__*/" | ||
}); | ||
// We can safely replace the escaped base now, since the `base` section is | ||
// now in a normalized escaped value. | ||
ast.walkClasses((node)=>{ | ||
if (node.value === base) { | ||
node.replaceWith(...formatAst.nodes); | ||
selector.walkClasses((node)=>{ | ||
if (node.value !== base) { | ||
return; | ||
} | ||
let parent = node.parent; | ||
let formatNodes = formatAst.nodes[0].nodes; | ||
// Perf optimization: if the parent is a single class we can just replace it and be done | ||
if (parent.nodes.length === 1) { | ||
node.replaceWith(...formatNodes); | ||
return; | ||
} | ||
let simpleSelector = simpleSelectorForNode(node); | ||
parent.insertBefore(simpleSelector[0], simpleStart); | ||
parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd); | ||
for (let child of formatNodes){ | ||
parent.insertBefore(simpleSelector[0], child.clone()); | ||
} | ||
node.remove(); | ||
// Re-sort the simple selector to ensure it's in the correct order | ||
simpleSelector = simpleSelectorForNode(simpleStart); | ||
let firstNode = parent.index(simpleStart); | ||
parent.nodes.splice(firstNode, simpleSelector.length, ...resortSelector(_postcssselectorparser.default.selector({ | ||
nodes: simpleSelector | ||
})).nodes); | ||
simpleStart.remove(); | ||
simpleEnd.remove(); | ||
}); | ||
// This will make sure to move pseudo's to the correct spot (the end for | ||
// pseudo elements) because otherwise the selector will never work | ||
// anyway. | ||
// | ||
// E.g.: | ||
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` | ||
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` | ||
// | ||
// `::before:hover` doesn't work, which means that we can make it work for you by flipping the order. | ||
function collectPseudoElements(selector) { | ||
let nodes = []; | ||
for (let node of selector.nodes){ | ||
if (isPseudoElement(node)) { | ||
nodes.push(node); | ||
selector.removeChild(node); | ||
} | ||
if (node === null || node === void 0 ? void 0 : node.nodes) { | ||
nodes.push(...collectPseudoElements(node)); | ||
} | ||
} | ||
return nodes; | ||
} | ||
// Remove unnecessary pseudo selectors that we used as placeholders | ||
ast.each((selector)=>{ | ||
selector.walkPseudos((p)=>{ | ||
if (selectorFunctions.has(p.value)) { | ||
p.replaceWith(p.nodes); | ||
} | ||
}); | ||
let pseudoElements = collectPseudoElements(selector); | ||
if (pseudoElements.length > 0) { | ||
selector.nodes.push(pseudoElements.sort(sortSelector)); | ||
selector.walkPseudos((p)=>{ | ||
if (p.value === MERGE) { | ||
p.replaceWith(p.nodes); | ||
} | ||
}); | ||
return ast.toString(); | ||
// Move pseudo elements to the end of the selector (if necessary) | ||
selector.each((sel)=>(0, _pseudoElements.movePseudos)(sel)); | ||
return selector.toString(); | ||
} | ||
// Note: As a rule, double colons (::) should be used instead of a single colon | ||
// (:). This distinguishes pseudo-classes from pseudo-elements. However, since | ||
// this distinction was not present in older versions of the W3C spec, most | ||
// browsers support both syntaxes for the original pseudo-elements. | ||
let pseudoElementsBC = [ | ||
":before", | ||
":after", | ||
":first-line", | ||
":first-letter" | ||
]; | ||
// These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter. | ||
let pseudoElementExceptions = [ | ||
"::file-selector-button" | ||
]; | ||
// This will make sure to move pseudo's to the correct spot (the end for | ||
// pseudo elements) because otherwise the selector will never work | ||
// anyway. | ||
// | ||
// E.g.: | ||
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` | ||
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` | ||
// | ||
// `::before:hover` doesn't work, which means that we can make it work | ||
// for you by flipping the order. | ||
function sortSelector(a, z) { | ||
// Both nodes are non-pseudo's so we can safely ignore them and keep | ||
// them in the same order. | ||
if (a.type !== "pseudo" && z.type !== "pseudo") { | ||
return 0; | ||
} | ||
// If one of them is a combinator, we need to keep it in the same order | ||
// because that means it will start a new "section" in the selector. | ||
if (a.type === "combinator" ^ z.type === "combinator") { | ||
return 0; | ||
} | ||
// One of the items is a pseudo and the other one isn't. Let's move | ||
// the pseudo to the right. | ||
if (a.type === "pseudo" ^ z.type === "pseudo") { | ||
return (a.type === "pseudo") - (z.type === "pseudo"); | ||
} | ||
// Both are pseudo's, move the pseudo elements (except for | ||
// ::file-selector-button) to the right. | ||
return isPseudoElement(a) - isPseudoElement(z); | ||
} | ||
function isPseudoElement(node) { | ||
if (node.type !== "pseudo") return false; | ||
if (pseudoElementExceptions.includes(node.value)) return false; | ||
return node.value.startsWith("::") || pseudoElementsBC.includes(node.value); | ||
} | ||
function resolveFunctionArgument(haystack, needle, arg) { | ||
let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle); | ||
if (startIdx === -1) return null; | ||
// Start inside the `(` | ||
startIdx += needle.length + 1; | ||
let target = ""; | ||
let count = 0; | ||
for (let char of haystack.slice(startIdx)){ | ||
if (char !== "(" && char !== ")") { | ||
target += char; | ||
} else if (char === "(") { | ||
target += char; | ||
count++; | ||
} else if (char === ")") { | ||
if (--count < 0) break; // unbalanced | ||
target += char; | ||
function handleMergePseudo(selector, format) { | ||
/** @type {{pseudo: Pseudo, value: string}[]} */ let merges = []; | ||
// Find all :merge() pseudo-classes in `selector` | ||
selector.walkPseudos((pseudo)=>{ | ||
if (pseudo.value === MERGE) { | ||
merges.push({ | ||
pseudo, | ||
value: pseudo.nodes[0].toString() | ||
}); | ||
} | ||
} | ||
return target; | ||
}); | ||
// Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector` | ||
format.walkPseudos((pseudo)=>{ | ||
if (pseudo.value !== MERGE) { | ||
return; | ||
} | ||
let value = pseudo.nodes[0].toString(); | ||
// Does `selector` contain a :merge() pseudo-class with the same value? | ||
let existing = merges.find((merge)=>merge.value === value); | ||
// Nope so there's nothing to do | ||
if (!existing) { | ||
return; | ||
} | ||
// Everything after `:merge()` up to the next combinator is what is attached to the merged selector | ||
let attachments = []; | ||
let next = pseudo.next(); | ||
while(next && next.type !== "combinator"){ | ||
attachments.push(next); | ||
next = next.next(); | ||
} | ||
let combinator = next; | ||
existing.pseudo.parent.insertAfter(existing.pseudo, _postcssselectorparser.default.selector({ | ||
nodes: attachments.map((node)=>node.clone()) | ||
})); | ||
pseudo.remove(); | ||
attachments.forEach((node)=>node.remove()); | ||
// What about this case: | ||
// :merge(.group):focus > & | ||
// :merge(.group):hover & | ||
if (combinator && combinator.type === "combinator") { | ||
combinator.remove(); | ||
} | ||
}); | ||
return [ | ||
selector, | ||
format | ||
]; | ||
} |
@@ -5,16 +5,42 @@ "use strict"; | ||
}); | ||
exports.default = getAllConfigs; | ||
var _defaultConfigStubJs = _interopRequireDefault(require("../../stubs/defaultConfig.stub.js")); | ||
var _featureFlags = require("../featureFlags"); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return getAllConfigs; | ||
} | ||
}); | ||
const _configfull = /*#__PURE__*/ _interop_require_default(require("../../stubs/config.full.js")); | ||
const _featureFlags = require("../featureFlags"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function getAllConfigs(config) { | ||
var ref; | ||
const configs = ((ref = config === null || config === void 0 ? void 0 : config.presets) !== null && ref !== void 0 ? ref : [ | ||
_defaultConfigStubJs.default | ||
]).slice().reverse().flatMap((preset)=>getAllConfigs(preset instanceof Function ? preset() : preset) | ||
); | ||
var _config_presets; | ||
const configs = ((_config_presets = config === null || config === void 0 ? void 0 : config.presets) !== null && _config_presets !== void 0 ? _config_presets : [ | ||
_configfull.default | ||
]).slice().reverse().flatMap((preset)=>getAllConfigs(preset instanceof Function ? preset() : preset)); | ||
const features = { | ||
// Add experimental configs here... | ||
respectDefaultRingColorOpacity: { | ||
theme: { | ||
ringColor: ({ theme })=>({ | ||
DEFAULT: "#3b82f67f", | ||
...theme("colors") | ||
}) | ||
} | ||
}, | ||
disableColorOpacityUtilitiesByDefault: { | ||
corePlugins: { | ||
backgroundOpacity: false, | ||
borderOpacity: false, | ||
divideOpacity: false, | ||
placeholderOpacity: false, | ||
ringOpacity: false, | ||
textOpacity: false | ||
} | ||
} | ||
}; | ||
const experimentals = Object.keys(features).filter((feature)=>(0, _featureFlags).flagEnabled(config, feature) | ||
).map((feature)=>features[feature] | ||
); | ||
const experimentals = Object.keys(features).filter((feature)=>(0, _featureFlags.flagEnabled)(config, feature)).map((feature)=>features[feature]); | ||
return [ | ||
@@ -26,6 +52,1 @@ config, | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,13 +5,18 @@ "use strict"; | ||
}); | ||
exports.default = hashConfig; | ||
var _objectHash = _interopRequireDefault(require("object-hash")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return hashConfig; | ||
} | ||
}); | ||
const _objecthash = /*#__PURE__*/ _interop_require_default(require("object-hash")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function hashConfig(config) { | ||
return (0, _objectHash).default(config, { | ||
return (0, _objecthash.default)(config, { | ||
ignoreUnknown: true | ||
}); | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.default = isKeyframeRule; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return isKeyframeRule; | ||
} | ||
}); | ||
function isKeyframeRule(rule) { | ||
return rule.parent && rule.parent.type === "atrule" && /keyframes$/.test(rule.parent.name); | ||
} |
@@ -5,3 +5,8 @@ "use strict"; | ||
}); | ||
exports.default = isPlainObject; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return isPlainObject; | ||
} | ||
}); | ||
function isPlainObject(value) { | ||
@@ -12,3 +17,3 @@ if (Object.prototype.toString.call(value) !== "[object Object]") { | ||
const prototype = Object.getPrototypeOf(value); | ||
return prototype === null || prototype === Object.prototype; | ||
return prototype === null || Object.getPrototypeOf(prototype) === null; | ||
} |
@@ -5,6 +5,18 @@ "use strict"; | ||
}); | ||
exports.dim = dim; | ||
exports.default = void 0; | ||
var _picocolors = _interopRequireDefault(require("picocolors")); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
dim: function() { | ||
return dim; | ||
}, | ||
default: function() { | ||
return _default; | ||
} | ||
}); | ||
const _picocolors = /*#__PURE__*/ _interop_require_default(require("picocolors")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -16,8 +28,7 @@ default: obj | ||
function log(type, messages, key) { | ||
if (process.env.JEST_WORKER_ID !== undefined) return; | ||
if (typeof process !== "undefined" && process.env.JEST_WORKER_ID) return; | ||
if (key && alreadyShown.has(key)) return; | ||
if (key) alreadyShown.add(key); | ||
console.warn(""); | ||
messages.forEach((message)=>console.warn(type, "-", message) | ||
); | ||
messages.forEach((message)=>console.warn(type, "-", message)); | ||
} | ||
@@ -27,3 +38,3 @@ function dim(input) { | ||
} | ||
var _default = { | ||
const _default = { | ||
info (key, messages) { | ||
@@ -54,2 +65,1 @@ log(_picocolors.default.bold(_picocolors.default.cyan("info")), ...Array.isArray(key) ? [ | ||
}; | ||
exports.default = _default; |
@@ -5,11 +5,22 @@ "use strict"; | ||
}); | ||
exports.default = nameClass; | ||
exports.asClass = asClass; | ||
exports.formatClass = formatClass; | ||
var _escapeClassName = _interopRequireDefault(require("./escapeClassName")); | ||
var _escapeCommas = _interopRequireDefault(require("./escapeCommas")); | ||
function nameClass(classPrefix, key) { | ||
return asClass(formatClass(classPrefix, key)); | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
function _interopRequireDefault(obj) { | ||
_export(exports, { | ||
asClass: function() { | ||
return asClass; | ||
}, | ||
default: function() { | ||
return nameClass; | ||
}, | ||
formatClass: function() { | ||
return formatClass; | ||
} | ||
}); | ||
const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("./escapeClassName")); | ||
const _escapeCommas = /*#__PURE__*/ _interop_require_default(require("./escapeCommas")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -20,4 +31,7 @@ default: obj | ||
function asClass(name) { | ||
return (0, _escapeCommas).default(`.${(0, _escapeClassName).default(name)}`); | ||
return (0, _escapeCommas.default)(`.${(0, _escapeClassName.default)(name)}`); | ||
} | ||
function nameClass(classPrefix, key) { | ||
return asClass(formatClass(classPrefix, key)); | ||
} | ||
function formatClass(classPrefix, key) { | ||
@@ -33,3 +47,6 @@ if (key === "DEFAULT") { | ||
} | ||
if (key.startsWith("/")) { | ||
return `${classPrefix}${key}`; | ||
} | ||
return `${classPrefix}-${key}`; | ||
} |
@@ -5,4 +5,9 @@ "use strict"; | ||
}); | ||
exports.default = _default; | ||
function _default(value) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return negateValue; | ||
} | ||
}); | ||
function negateValue(value) { | ||
value = `${value}`; | ||
@@ -14,8 +19,20 @@ if (value === "0") { | ||
if (/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(value)) { | ||
return value.replace(/^[+-]?/, (sign)=>sign === "-" ? "" : "-" | ||
); | ||
return value.replace(/^[+-]?/, (sign)=>sign === "-" ? "" : "-"); | ||
} | ||
if (value.includes("var(") || value.includes("calc(")) { | ||
return `calc(${value} * -1)`; | ||
// What functions we support negating numeric values for | ||
// var() isn't inherently a numeric function but we support it anyway | ||
// The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_ | ||
// to produce generally useful results and that will be covered already | ||
let numericFunctions = [ | ||
"var", | ||
"calc", | ||
"min", | ||
"max", | ||
"clamp" | ||
]; | ||
for (const fn of numericFunctions){ | ||
if (value.includes(`${fn}(`)) { | ||
return `calc(${value} * -1)`; | ||
} | ||
} | ||
} |
@@ -5,24 +5,48 @@ "use strict"; | ||
}); | ||
exports.normalizeConfig = normalizeConfig; | ||
var _log = _interopRequireWildcard(require("./log")); | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
Object.defineProperty(exports, "normalizeConfig", { | ||
enumerable: true, | ||
get: function() { | ||
return normalizeConfig; | ||
} | ||
}); | ||
const _featureFlags = require("../featureFlags"); | ||
const _log = /*#__PURE__*/ _interop_require_wildcard(require("./log")); | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interop_require_wildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
} | ||
@@ -74,9 +98,9 @@ function normalizeConfig(config) { | ||
if (typeof config.content === "object" && config.content !== null) { | ||
// Only `files`, `extract` and `transform` can exist in `config.content` | ||
// Only `files`, `relative`, `extract`, and `transform` can exist in `config.content` | ||
if (Object.keys(config.content).some((key)=>![ | ||
"files", | ||
"relative", | ||
"extract", | ||
"transform" | ||
].includes(key) | ||
)) { | ||
].includes(key))) { | ||
return false; | ||
@@ -120,2 +144,6 @@ } | ||
} | ||
// `config.content.relative` is optional and can be a boolean | ||
if (typeof config.content.relative !== "boolean" && typeof config.content.relative !== "undefined") { | ||
return false; | ||
} | ||
} | ||
@@ -130,3 +158,3 @@ return true; | ||
"Update your configuration file to eliminate this warning.", | ||
"https://tailwindcss.com/docs/upgrade-guide#configure-content-sources", | ||
"https://tailwindcss.com/docs/upgrade-guide#configure-content-sources" | ||
]); | ||
@@ -136,3 +164,3 @@ } | ||
config.safelist = (()=>{ | ||
var ref; | ||
var _purge_options; | ||
let { content , purge , safelist } = config; | ||
@@ -142,5 +170,19 @@ if (Array.isArray(safelist)) return safelist; | ||
if (Array.isArray(purge === null || purge === void 0 ? void 0 : purge.safelist)) return purge.safelist; | ||
if (Array.isArray(purge === null || purge === void 0 ? void 0 : (ref = purge.options) === null || ref === void 0 ? void 0 : ref.safelist)) return purge.options.safelist; | ||
if (Array.isArray(purge === null || purge === void 0 ? void 0 : (_purge_options = purge.options) === null || _purge_options === void 0 ? void 0 : _purge_options.safelist)) return purge.options.safelist; | ||
return []; | ||
})(); | ||
// Normalize the `blocklist` | ||
config.blocklist = (()=>{ | ||
let { blocklist } = config; | ||
if (Array.isArray(blocklist)) { | ||
if (blocklist.every((item)=>typeof item === "string")) { | ||
return blocklist; | ||
} | ||
_log.default.warn("blocklist-invalid", [ | ||
"The `blocklist` option must be an array of strings.", | ||
"https://tailwindcss.com/docs/content-configuration#discarding-classes" | ||
]); | ||
} | ||
return []; | ||
})(); | ||
// Normalize prefix option | ||
@@ -151,11 +193,18 @@ if (typeof config.prefix === "function") { | ||
"Update `prefix` in your configuration to be a string to eliminate this warning.", | ||
"https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function", | ||
"https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function" | ||
]); | ||
config.prefix = ""; | ||
} else { | ||
var _prefix; | ||
config.prefix = (_prefix = config.prefix) !== null && _prefix !== void 0 ? _prefix : ""; | ||
var _config_prefix; | ||
config.prefix = (_config_prefix = config.prefix) !== null && _config_prefix !== void 0 ? _config_prefix : ""; | ||
} | ||
// Normalize the `content` | ||
config.content = { | ||
relative: (()=>{ | ||
let { content } = config; | ||
if (content === null || content === void 0 ? void 0 : content.relative) { | ||
return content.relative; | ||
} | ||
return (0, _featureFlags.flagEnabled)(config, "relativeContentPathsByDefault"); | ||
})(), | ||
files: (()=>{ | ||
@@ -172,9 +221,9 @@ let { content , purge } = config; | ||
let extract = (()=>{ | ||
var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9; | ||
if ((ref = config.purge) === null || ref === void 0 ? void 0 : ref.extract) return config.purge.extract; | ||
if ((ref1 = config.content) === null || ref1 === void 0 ? void 0 : ref1.extract) return config.content.extract; | ||
if ((ref2 = config.purge) === null || ref2 === void 0 ? void 0 : (ref3 = ref2.extract) === null || ref3 === void 0 ? void 0 : ref3.DEFAULT) return config.purge.extract.DEFAULT; | ||
if ((ref4 = config.content) === null || ref4 === void 0 ? void 0 : (ref5 = ref4.extract) === null || ref5 === void 0 ? void 0 : ref5.DEFAULT) return config.content.extract.DEFAULT; | ||
if ((ref6 = config.purge) === null || ref6 === void 0 ? void 0 : (ref7 = ref6.options) === null || ref7 === void 0 ? void 0 : ref7.extractors) return config.purge.options.extractors; | ||
if ((ref8 = config.content) === null || ref8 === void 0 ? void 0 : (ref9 = ref8.options) === null || ref9 === void 0 ? void 0 : ref9.extractors) return config.content.options.extractors; | ||
var _config_purge, _config_content, _config_purge1, _config_purge_extract, _config_content1, _config_content_extract, _config_purge2, _config_purge_options, _config_content2, _config_content_options; | ||
if ((_config_purge = config.purge) === null || _config_purge === void 0 ? void 0 : _config_purge.extract) return config.purge.extract; | ||
if ((_config_content = config.content) === null || _config_content === void 0 ? void 0 : _config_content.extract) return config.content.extract; | ||
if ((_config_purge1 = config.purge) === null || _config_purge1 === void 0 ? void 0 : (_config_purge_extract = _config_purge1.extract) === null || _config_purge_extract === void 0 ? void 0 : _config_purge_extract.DEFAULT) return config.purge.extract.DEFAULT; | ||
if ((_config_content1 = config.content) === null || _config_content1 === void 0 ? void 0 : (_config_content_extract = _config_content1.extract) === null || _config_content_extract === void 0 ? void 0 : _config_content_extract.DEFAULT) return config.content.extract.DEFAULT; | ||
if ((_config_purge2 = config.purge) === null || _config_purge2 === void 0 ? void 0 : (_config_purge_options = _config_purge2.options) === null || _config_purge_options === void 0 ? void 0 : _config_purge_options.extractors) return config.purge.options.extractors; | ||
if ((_config_content2 = config.content) === null || _config_content2 === void 0 ? void 0 : (_config_content_options = _config_content2.options) === null || _config_content_options === void 0 ? void 0 : _config_content_options.extractors) return config.content.options.extractors; | ||
return {}; | ||
@@ -184,7 +233,7 @@ })(); | ||
let defaultExtractor = (()=>{ | ||
var ref, ref10, ref11, ref12; | ||
if ((ref = config.purge) === null || ref === void 0 ? void 0 : (ref10 = ref.options) === null || ref10 === void 0 ? void 0 : ref10.defaultExtractor) { | ||
var _config_purge, _config_purge_options, _config_content, _config_content_options; | ||
if ((_config_purge = config.purge) === null || _config_purge === void 0 ? void 0 : (_config_purge_options = _config_purge.options) === null || _config_purge_options === void 0 ? void 0 : _config_purge_options.defaultExtractor) { | ||
return config.purge.options.defaultExtractor; | ||
} | ||
if ((ref11 = config.content) === null || ref11 === void 0 ? void 0 : (ref12 = ref11.options) === null || ref12 === void 0 ? void 0 : ref12.defaultExtractor) { | ||
if ((_config_content = config.content) === null || _config_content === void 0 ? void 0 : (_config_content_options = _config_content.options) === null || _config_content_options === void 0 ? void 0 : _config_content_options.defaultExtractor) { | ||
return config.content.options.defaultExtractor; | ||
@@ -213,7 +262,7 @@ } | ||
let transform = (()=>{ | ||
var ref, ref13, ref14, ref15, ref16, ref17; | ||
if ((ref = config.purge) === null || ref === void 0 ? void 0 : ref.transform) return config.purge.transform; | ||
if ((ref13 = config.content) === null || ref13 === void 0 ? void 0 : ref13.transform) return config.content.transform; | ||
if ((ref14 = config.purge) === null || ref14 === void 0 ? void 0 : (ref15 = ref14.transform) === null || ref15 === void 0 ? void 0 : ref15.DEFAULT) return config.purge.transform.DEFAULT; | ||
if ((ref16 = config.content) === null || ref16 === void 0 ? void 0 : (ref17 = ref16.transform) === null || ref17 === void 0 ? void 0 : ref17.DEFAULT) return config.content.transform.DEFAULT; | ||
var _config_purge, _config_content, _config_purge1, _config_purge_transform, _config_content1, _config_content_transform; | ||
if ((_config_purge = config.purge) === null || _config_purge === void 0 ? void 0 : _config_purge.transform) return config.purge.transform; | ||
if ((_config_content = config.content) === null || _config_content === void 0 ? void 0 : _config_content.transform) return config.content.transform; | ||
if ((_config_purge1 = config.purge) === null || _config_purge1 === void 0 ? void 0 : (_config_purge_transform = _config_purge1.transform) === null || _config_purge_transform === void 0 ? void 0 : _config_purge_transform.DEFAULT) return config.purge.transform.DEFAULT; | ||
if ((_config_content1 = config.content) === null || _config_content1 === void 0 ? void 0 : (_config_content_transform = _config_content1.transform) === null || _config_content_transform === void 0 ? void 0 : _config_content_transform.DEFAULT) return config.content.transform.DEFAULT; | ||
return {}; | ||
@@ -224,4 +273,3 @@ })(); | ||
transformers.DEFAULT = transform; | ||
} | ||
if (typeof transform === "object" && transform !== null) { | ||
} else if (typeof transform === "object" && transform !== null) { | ||
Object.assign(transformers, transform); | ||
@@ -237,4 +285,4 @@ } | ||
_log.default.warn("invalid-glob-braces", [ | ||
`The glob pattern ${(0, _log).dim(file)} in your Tailwind CSS configuration is invalid.`, | ||
`Update it to ${(0, _log).dim(file.replace(/{([^,]*?)}/g, "$1"))} to silence this warning.` | ||
`The glob pattern ${(0, _log.dim)(file)} in your Tailwind CSS configuration is invalid.`, | ||
`Update it to ${(0, _log.dim)(file.replace(/{([^,]*?)}/g, "$1"))} to silence this warning.` | ||
]); | ||
@@ -241,0 +289,0 @@ break; |
@@ -1,6 +0,49 @@ | ||
"use strict"; | ||
/** | ||
* @typedef {object} ScreenValue | ||
* @property {number|undefined} min | ||
* @property {number|undefined} max | ||
* @property {string|undefined} raw | ||
*/ /** | ||
* @typedef {object} Screen | ||
* @property {string} name | ||
* @property {boolean} not | ||
* @property {ScreenValue[]} values | ||
*/ /** | ||
* A function that normalizes the various forms that the screens object can be | ||
* provided in. | ||
* | ||
* Input(s): | ||
* - ['100px', '200px'] // Raw strings | ||
* - { sm: '100px', md: '200px' } // Object with string values | ||
* - { sm: { min: '100px' }, md: { max: '100px' } } // Object with object values | ||
* - { sm: [{ min: '100px' }, { max: '200px' }] } // Object with object array (multiple values) | ||
* | ||
* Output(s): | ||
* - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values | ||
* | ||
* @returns {Screen[]} | ||
*/ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.normalizeScreens = normalizeScreens; | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
normalizeScreens: function() { | ||
return normalizeScreens; | ||
}, | ||
isScreenSortable: function() { | ||
return isScreenSortable; | ||
}, | ||
compareScreens: function() { | ||
return compareScreens; | ||
}, | ||
toScreen: function() { | ||
return toScreen; | ||
} | ||
}); | ||
function normalizeScreens(screens, root = true) { | ||
@@ -15,2 +58,3 @@ if (Array.isArray(screens)) { | ||
name: screen.toString(), | ||
not: false, | ||
values: [ | ||
@@ -29,2 +73,3 @@ { | ||
name, | ||
not: false, | ||
values: [ | ||
@@ -41,4 +86,4 @@ { | ||
name, | ||
values: options.map((option)=>resolveValue(option) | ||
) | ||
not: false, | ||
values: options.map((option)=>resolveValue(option)) | ||
}; | ||
@@ -48,2 +93,3 @@ } | ||
name, | ||
not: false, | ||
values: [ | ||
@@ -57,2 +103,75 @@ resolveValue(options) | ||
} | ||
function isScreenSortable(screen) { | ||
if (screen.values.length !== 1) { | ||
return { | ||
result: false, | ||
reason: "multiple-values" | ||
}; | ||
} else if (screen.values[0].raw !== undefined) { | ||
return { | ||
result: false, | ||
reason: "raw-values" | ||
}; | ||
} else if (screen.values[0].min !== undefined && screen.values[0].max !== undefined) { | ||
return { | ||
result: false, | ||
reason: "min-and-max" | ||
}; | ||
} | ||
return { | ||
result: true, | ||
reason: null | ||
}; | ||
} | ||
function compareScreens(type, a, z) { | ||
let aScreen = toScreen(a, type); | ||
let zScreen = toScreen(z, type); | ||
let aSorting = isScreenSortable(aScreen); | ||
let bSorting = isScreenSortable(zScreen); | ||
// These cases should never happen and indicate a bug in Tailwind CSS itself | ||
if (aSorting.reason === "multiple-values" || bSorting.reason === "multiple-values") { | ||
throw new Error("Attempted to sort a screen with multiple values. This should never happen. Please open a bug report."); | ||
} else if (aSorting.reason === "raw-values" || bSorting.reason === "raw-values") { | ||
throw new Error("Attempted to sort a screen with raw values. This should never happen. Please open a bug report."); | ||
} else if (aSorting.reason === "min-and-max" || bSorting.reason === "min-and-max") { | ||
throw new Error("Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report."); | ||
} | ||
// Let the sorting begin | ||
let { min: aMin , max: aMax } = aScreen.values[0]; | ||
let { min: zMin , max: zMax } = zScreen.values[0]; | ||
// Negating screens flip their behavior. Basically `not min-width` is `max-width` | ||
if (a.not) [aMin, aMax] = [ | ||
aMax, | ||
aMin | ||
]; | ||
if (z.not) [zMin, zMax] = [ | ||
zMax, | ||
zMin | ||
]; | ||
aMin = aMin === undefined ? aMin : parseFloat(aMin); | ||
aMax = aMax === undefined ? aMax : parseFloat(aMax); | ||
zMin = zMin === undefined ? zMin : parseFloat(zMin); | ||
zMax = zMax === undefined ? zMax : parseFloat(zMax); | ||
let [aValue, zValue] = type === "min" ? [ | ||
aMin, | ||
zMin | ||
] : [ | ||
zMax, | ||
aMax | ||
]; | ||
return aValue - zValue; | ||
} | ||
function toScreen(value, type) { | ||
if (typeof value === "object") { | ||
return value; | ||
} | ||
return { | ||
name: "arbitrary-screen", | ||
values: [ | ||
{ | ||
[type]: value | ||
} | ||
] | ||
}; | ||
} | ||
function resolveValue({ "min-width": _minWidth , min =_minWidth , max , raw } = {}) { | ||
@@ -59,0 +178,0 @@ return { |
@@ -5,3 +5,46 @@ "use strict"; | ||
}); | ||
exports.default = parseAnimationValue; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return parseAnimationValue; | ||
} | ||
}); | ||
const DIRECTIONS = new Set([ | ||
"normal", | ||
"reverse", | ||
"alternate", | ||
"alternate-reverse" | ||
]); | ||
const PLAY_STATES = new Set([ | ||
"running", | ||
"paused" | ||
]); | ||
const FILL_MODES = new Set([ | ||
"none", | ||
"forwards", | ||
"backwards", | ||
"both" | ||
]); | ||
const ITERATION_COUNTS = new Set([ | ||
"infinite" | ||
]); | ||
const TIMINGS = new Set([ | ||
"linear", | ||
"ease", | ||
"ease-in", | ||
"ease-out", | ||
"ease-in-out", | ||
"step-start", | ||
"step-end" | ||
]); | ||
const TIMING_FNS = [ | ||
"cubic-bezier", | ||
"steps" | ||
]; | ||
const COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubic-bezier(a, b, c)` these don't count. | ||
; | ||
const SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead. | ||
; | ||
const TIME = /^(-?[\d.]+m?s)$/; | ||
const DIGIT = /^(\d+)$/; | ||
function parseAnimationValue(input) { | ||
@@ -32,4 +75,3 @@ let animations = input.split(COMMA); | ||
seen.add("TIMING_FUNCTION"); | ||
} else if (!seen.has("TIMING_FUNCTION") && TIMING_FNS.some((f)=>part.startsWith(`${f}(`) | ||
)) { | ||
} else if (!seen.has("TIMING_FUNCTION") && TIMING_FNS.some((f)=>part.startsWith(`${f}(`))) { | ||
result.timingFunction = part; | ||
@@ -54,39 +96,1 @@ seen.add("TIMING_FUNCTION"); | ||
} | ||
const DIRECTIONS = new Set([ | ||
"normal", | ||
"reverse", | ||
"alternate", | ||
"alternate-reverse" | ||
]); | ||
const PLAY_STATES = new Set([ | ||
"running", | ||
"paused" | ||
]); | ||
const FILL_MODES = new Set([ | ||
"none", | ||
"forwards", | ||
"backwards", | ||
"both" | ||
]); | ||
const ITERATION_COUNTS = new Set([ | ||
"infinite" | ||
]); | ||
const TIMINGS = new Set([ | ||
"linear", | ||
"ease", | ||
"ease-in", | ||
"ease-out", | ||
"ease-in-out", | ||
"step-start", | ||
"step-end", | ||
]); | ||
const TIMING_FNS = [ | ||
"cubic-bezier", | ||
"steps" | ||
]; | ||
const COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. | ||
; | ||
const SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead. | ||
; | ||
const TIME = /^(-?[\d.]+m?s)$/; | ||
const DIGIT = /^(\d+)$/; |
@@ -5,5 +5,17 @@ "use strict"; | ||
}); | ||
exports.parseBoxShadowValue = parseBoxShadowValue; | ||
exports.formatBoxShadowValue = formatBoxShadowValue; | ||
var _splitAtTopLevelOnly = require("./splitAtTopLevelOnly"); | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
parseBoxShadowValue: function() { | ||
return parseBoxShadowValue; | ||
}, | ||
formatBoxShadowValue: function() { | ||
return formatBoxShadowValue; | ||
} | ||
}); | ||
const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly"); | ||
let KEYWORDS = new Set([ | ||
@@ -20,3 +32,3 @@ "inset", | ||
function parseBoxShadowValue(input) { | ||
let shadows = Array.from((0, _splitAtTopLevelOnly).splitAtTopLevelOnly(input, ",")); | ||
let shadows = (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(input, ","); | ||
return shadows.map((shadow)=>{ | ||
@@ -23,0 +35,0 @@ let value = shadow.trim(); |
@@ -1,63 +0,47 @@ | ||
"use strict"; | ||
// @ts-check | ||
/** | ||
* @typedef {{type: 'dependency', file: string} | {type: 'dir-dependency', dir: string, glob: string}} Dependency | ||
*/ /** | ||
* | ||
* @param {import('../lib/content.js').ContentPath} contentPath | ||
* @returns {Dependency[]} | ||
*/ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = parseDependency; | ||
var _isGlob = _interopRequireDefault(require("is-glob")); | ||
var _globParent = _interopRequireDefault(require("glob-parent")); | ||
var _path = _interopRequireDefault(require("path")); | ||
function parseDependency(normalizedFileOrGlob) { | ||
if (normalizedFileOrGlob.startsWith("!")) { | ||
return null; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return parseDependency; | ||
} | ||
let message; | ||
if ((0, _isGlob).default(normalizedFileOrGlob)) { | ||
let { base , glob } = parseGlob(normalizedFileOrGlob); | ||
message = { | ||
type: "dir-dependency", | ||
dir: _path.default.resolve(base), | ||
glob | ||
}; | ||
} else { | ||
message = { | ||
type: "dependency", | ||
file: _path.default.resolve(normalizedFileOrGlob) | ||
}; | ||
}); | ||
function parseDependency(contentPath) { | ||
if (contentPath.ignore) { | ||
return []; | ||
} | ||
// rollup-plugin-postcss does not support dir-dependency messages | ||
// but directories can be watched in the same way as files | ||
if (message.type === "dir-dependency" && process.env.ROLLUP_WATCH === "true") { | ||
message = { | ||
type: "dependency", | ||
file: message.dir | ||
}; | ||
if (!contentPath.glob) { | ||
return [ | ||
{ | ||
type: "dependency", | ||
file: contentPath.base | ||
} | ||
]; | ||
} | ||
return message; | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
// Based on `glob-base` | ||
// https://github.com/micromatch/glob-base/blob/master/index.js | ||
function parseGlob(pattern) { | ||
let glob = pattern; | ||
let base = (0, _globParent).default(pattern); | ||
if (base !== ".") { | ||
glob = pattern.substr(base.length); | ||
if (glob.charAt(0) === "/") { | ||
glob = glob.substr(1); | ||
if (process.env.ROLLUP_WATCH === "true") { | ||
// rollup-plugin-postcss does not support dir-dependency messages | ||
// but directories can be watched in the same way as files | ||
return [ | ||
{ | ||
type: "dependency", | ||
file: contentPath.base | ||
} | ||
]; | ||
} | ||
return [ | ||
{ | ||
type: "dir-dependency", | ||
dir: contentPath.base, | ||
glob: contentPath.glob | ||
} | ||
} | ||
if (glob.substr(0, 2) === "./") { | ||
glob = glob.substr(2); | ||
} | ||
if (glob.charAt(0) === "/") { | ||
glob = glob.substr(1); | ||
} | ||
return { | ||
base, | ||
glob | ||
}; | ||
]; | ||
} |
@@ -5,6 +5,16 @@ "use strict"; | ||
}); | ||
exports.default = parseObjectStyles; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _postcssNested = _interopRequireDefault(require("postcss-nested")); | ||
var _postcssJs = _interopRequireDefault(require("postcss-js")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return parseObjectStyles; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _postcssnested = /*#__PURE__*/ _interop_require_default(require("postcss-nested")); | ||
const _postcssjs = /*#__PURE__*/ _interop_require_default(require("postcss-js")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function parseObjectStyles(styles) { | ||
@@ -17,17 +27,12 @@ if (!Array.isArray(styles)) { | ||
return styles.flatMap((style)=>{ | ||
return (0, _postcss).default([ | ||
(0, _postcssNested).default({ | ||
return (0, _postcss.default)([ | ||
(0, _postcssnested.default)({ | ||
bubble: [ | ||
"screen" | ||
] | ||
}), | ||
}) | ||
]).process(style, { | ||
parser: _postcssJs.default | ||
parser: _postcssjs.default | ||
}).root.nodes; | ||
}); | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,13 +5,41 @@ "use strict"; | ||
}); | ||
exports.updateAllClasses = updateAllClasses; | ||
exports.asValue = asValue; | ||
exports.asColor = asColor; | ||
exports.asLookupValue = asLookupValue; | ||
exports.coerceValue = coerceValue; | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _escapeCommas = _interopRequireDefault(require("./escapeCommas")); | ||
var _withAlphaVariable = require("./withAlphaVariable"); | ||
var _dataTypes = require("./dataTypes"); | ||
var _negateValue = _interopRequireDefault(require("./negateValue")); | ||
function _interopRequireDefault(obj) { | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
updateAllClasses: function() { | ||
return updateAllClasses; | ||
}, | ||
asValue: function() { | ||
return asValue; | ||
}, | ||
parseColorFormat: function() { | ||
return parseColorFormat; | ||
}, | ||
asColor: function() { | ||
return asColor; | ||
}, | ||
asLookupValue: function() { | ||
return asLookupValue; | ||
}, | ||
typeMap: function() { | ||
return typeMap; | ||
}, | ||
coerceValue: function() { | ||
return coerceValue; | ||
}, | ||
getMatchingTypes: function() { | ||
return getMatchingTypes; | ||
} | ||
}); | ||
const _escapeCommas = /*#__PURE__*/ _interop_require_default(require("./escapeCommas")); | ||
const _withAlphaVariable = require("./withAlphaVariable"); | ||
const _dataTypes = require("./dataTypes"); | ||
const _negateValue = /*#__PURE__*/ _interop_require_default(require("./negateValue")); | ||
const _validateFormalSyntax = require("./validateFormalSyntax"); | ||
const _featureFlags = require("../featureFlags.js"); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -21,14 +49,9 @@ default: obj | ||
} | ||
function updateAllClasses(selectors1, updateClass) { | ||
let parser = (0, _postcssSelectorParser).default((selectors)=>{ | ||
selectors.walkClasses((sel)=>{ | ||
let updatedClass = updateClass(sel.value); | ||
sel.value = updatedClass; | ||
if (sel.raws && sel.raws.value) { | ||
sel.raws.value = (0, _escapeCommas).default(sel.raws.value); | ||
} | ||
}); | ||
function updateAllClasses(selectors, updateClass) { | ||
selectors.walkClasses((sel)=>{ | ||
sel.value = updateClass(sel.value); | ||
if (sel.raws && sel.raws.value) { | ||
sel.raws.value = (0, _escapeCommas.default)(sel.raws.value); | ||
} | ||
}); | ||
let result = parser.processSync(selectors1); | ||
return result; | ||
} | ||
@@ -43,3 +66,3 @@ function resolveArbitraryValue(modifier, validate) { | ||
} | ||
return (0, _dataTypes).normalize(value); | ||
return (0, _dataTypes.normalize)(value); | ||
} | ||
@@ -49,3 +72,3 @@ function asNegativeValue(modifier, lookup = {}, validate) { | ||
if (positiveValue !== undefined) { | ||
return (0, _negateValue).default(positiveValue); | ||
return (0, _negateValue.default)(positiveValue); | ||
} | ||
@@ -57,9 +80,8 @@ if (isArbitraryValue(modifier)) { | ||
} | ||
return (0, _negateValue).default(resolved); | ||
return (0, _negateValue.default)(resolved); | ||
} | ||
} | ||
function asValue(modifier, options = {}, { validate =()=>true | ||
} = {}) { | ||
var ref; | ||
let value = (ref = options.values) === null || ref === void 0 ? void 0 : ref[modifier]; | ||
function asValue(modifier, options = {}, { validate =()=>true } = {}) { | ||
var _options_values; | ||
let value = (_options_values = options.values) === null || _options_values === void 0 ? void 0 : _options_values[modifier]; | ||
if (value !== undefined) { | ||
@@ -76,9 +98,33 @@ return value; | ||
} | ||
function splitAlpha(modifier) { | ||
function splitUtilityModifier(modifier) { | ||
let slashIdx = modifier.lastIndexOf("/"); | ||
// If the `/` is inside an arbitrary, we want to find the previous one if any | ||
// This logic probably isn't perfect but it should work for most cases | ||
let arbitraryStartIdx = modifier.lastIndexOf("[", slashIdx); | ||
let arbitraryEndIdx = modifier.indexOf("]", slashIdx); | ||
let isNextToArbitrary = modifier[slashIdx - 1] === "]" || modifier[slashIdx + 1] === "["; | ||
// Backtrack to the previous `/` if the one we found was inside an arbitrary | ||
if (!isNextToArbitrary) { | ||
if (arbitraryStartIdx !== -1 && arbitraryEndIdx !== -1) { | ||
if (arbitraryStartIdx < slashIdx && slashIdx < arbitraryEndIdx) { | ||
slashIdx = modifier.lastIndexOf("/", arbitraryStartIdx); | ||
} | ||
} | ||
} | ||
if (slashIdx === -1 || slashIdx === modifier.length - 1) { | ||
return [ | ||
modifier | ||
modifier, | ||
undefined | ||
]; | ||
} | ||
let arbitrary = isArbitraryValue(modifier); | ||
// The modifier could be of the form `[foo]/[bar]` | ||
// We want to handle this case properly | ||
// without affecting `[foo/bar]` | ||
if (arbitrary && !modifier.includes("]/[")) { | ||
return [ | ||
modifier, | ||
undefined | ||
]; | ||
} | ||
return [ | ||
@@ -89,23 +135,36 @@ modifier.slice(0, slashIdx), | ||
} | ||
function parseColorFormat(value) { | ||
if (typeof value === "string" && value.includes("<alpha-value>")) { | ||
let oldValue = value; | ||
return ({ opacityValue =1 })=>oldValue.replace(/<alpha-value>/g, opacityValue); | ||
} | ||
return value; | ||
} | ||
function unwrapArbitraryModifier(modifier) { | ||
return (0, _dataTypes.normalize)(modifier.slice(1, -1)); | ||
} | ||
function asColor(modifier, options = {}, { tailwindConfig ={} } = {}) { | ||
var ref; | ||
if (((ref = options.values) === null || ref === void 0 ? void 0 : ref[modifier]) !== undefined) { | ||
var ref1; | ||
return (ref1 = options.values) === null || ref1 === void 0 ? void 0 : ref1[modifier]; | ||
var _options_values; | ||
if (((_options_values = options.values) === null || _options_values === void 0 ? void 0 : _options_values[modifier]) !== undefined) { | ||
var _options_values1; | ||
return parseColorFormat((_options_values1 = options.values) === null || _options_values1 === void 0 ? void 0 : _options_values1[modifier]); | ||
} | ||
let [color, alpha] = splitAlpha(modifier); | ||
// TODO: Hoist this up to getMatchingTypes or something | ||
// We do this here because we need the alpha value (if any) | ||
let [color, alpha] = splitUtilityModifier(modifier); | ||
if (alpha !== undefined) { | ||
var ref2, ref3, ref4; | ||
var ref5; | ||
let normalizedColor = (ref5 = (ref2 = options.values) === null || ref2 === void 0 ? void 0 : ref2[color]) !== null && ref5 !== void 0 ? ref5 : isArbitraryValue(color) ? color.slice(1, -1) : undefined; | ||
var _options_values2, _tailwindConfig_theme, _tailwindConfig_theme_opacity; | ||
var _options_values_color; | ||
let normalizedColor = (_options_values_color = (_options_values2 = options.values) === null || _options_values2 === void 0 ? void 0 : _options_values2[color]) !== null && _options_values_color !== void 0 ? _options_values_color : isArbitraryValue(color) ? color.slice(1, -1) : undefined; | ||
if (normalizedColor === undefined) { | ||
return undefined; | ||
} | ||
normalizedColor = parseColorFormat(normalizedColor); | ||
if (isArbitraryValue(alpha)) { | ||
return (0, _withAlphaVariable).withAlphaValue(normalizedColor, alpha.slice(1, -1)); | ||
return (0, _withAlphaVariable.withAlphaValue)(normalizedColor, unwrapArbitraryModifier(alpha)); | ||
} | ||
if (((ref3 = tailwindConfig.theme) === null || ref3 === void 0 ? void 0 : (ref4 = ref3.opacity) === null || ref4 === void 0 ? void 0 : ref4[alpha]) === undefined) { | ||
if (((_tailwindConfig_theme = tailwindConfig.theme) === null || _tailwindConfig_theme === void 0 ? void 0 : (_tailwindConfig_theme_opacity = _tailwindConfig_theme.opacity) === null || _tailwindConfig_theme_opacity === void 0 ? void 0 : _tailwindConfig_theme_opacity[alpha]) === undefined) { | ||
return undefined; | ||
} | ||
return (0, _withAlphaVariable).withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha]); | ||
return (0, _withAlphaVariable.withAlphaValue)(normalizedColor, tailwindConfig.theme.opacity[alpha]); | ||
} | ||
@@ -117,4 +176,4 @@ return asValue(modifier, options, { | ||
function asLookupValue(modifier, options = {}) { | ||
var ref; | ||
return (ref = options.values) === null || ref === void 0 ? void 0 : ref[modifier]; | ||
var _options_values; | ||
return (_options_values = options.values) === null || _options_values === void 0 ? void 0 : _options_values[modifier]; | ||
} | ||
@@ -143,3 +202,4 @@ function guess(validate) { | ||
"relative-size": guess(_dataTypes.relativeSize), | ||
shadow: guess(_dataTypes.shadow) | ||
shadow: guess(_dataTypes.shadow), | ||
size: guess(_validateFormalSyntax.backgroundSize) | ||
}; | ||
@@ -159,2 +219,17 @@ let supportedTypes = Object.keys(typeMap); | ||
function coerceValue(types, modifier, options, tailwindConfig) { | ||
if (options.values && modifier in options.values) { | ||
for (let { type } of types !== null && types !== void 0 ? types : []){ | ||
let result = typeMap[type](modifier, options, { | ||
tailwindConfig | ||
}); | ||
if (result === undefined) { | ||
continue; | ||
} | ||
return [ | ||
result, | ||
type, | ||
null | ||
]; | ||
} | ||
} | ||
if (isArbitraryValue(modifier)) { | ||
@@ -174,17 +249,52 @@ let arbitraryValue = modifier.slice(1, -1); | ||
asValue(`[${value}]`, options), | ||
explicitType | ||
explicitType, | ||
null | ||
]; | ||
} | ||
} | ||
let matches = getMatchingTypes(types, modifier, options, tailwindConfig); | ||
// Find first matching type | ||
for (let type of [].concat(types)){ | ||
for (let match of matches){ | ||
return match; | ||
} | ||
return []; | ||
} | ||
function* getMatchingTypes(types, rawModifier, options, tailwindConfig) { | ||
let modifiersEnabled = (0, _featureFlags.flagEnabled)(tailwindConfig, "generalizedModifiers"); | ||
let [modifier, utilityModifier] = splitUtilityModifier(rawModifier); | ||
let canUseUtilityModifier = modifiersEnabled && options.modifiers != null && (options.modifiers === "any" || typeof options.modifiers === "object" && (utilityModifier && isArbitraryValue(utilityModifier) || utilityModifier in options.modifiers)); | ||
if (!canUseUtilityModifier) { | ||
modifier = rawModifier; | ||
utilityModifier = undefined; | ||
} | ||
if (utilityModifier !== undefined && modifier === "") { | ||
modifier = "DEFAULT"; | ||
} | ||
// Check the full value first | ||
// TODO: Move to asValue… somehow | ||
if (utilityModifier !== undefined) { | ||
if (typeof options.modifiers === "object") { | ||
var _options_modifiers; | ||
var _options_modifiers_utilityModifier; | ||
let configValue = (_options_modifiers_utilityModifier = (_options_modifiers = options.modifiers) === null || _options_modifiers === void 0 ? void 0 : _options_modifiers[utilityModifier]) !== null && _options_modifiers_utilityModifier !== void 0 ? _options_modifiers_utilityModifier : null; | ||
if (configValue !== null) { | ||
utilityModifier = configValue; | ||
} else if (isArbitraryValue(utilityModifier)) { | ||
utilityModifier = unwrapArbitraryModifier(utilityModifier); | ||
} | ||
} | ||
} | ||
for (let { type } of types !== null && types !== void 0 ? types : []){ | ||
let result = typeMap[type](modifier, options, { | ||
tailwindConfig | ||
}); | ||
if (result !== undefined) return [ | ||
if (result === undefined) { | ||
continue; | ||
} | ||
yield [ | ||
result, | ||
type | ||
type, | ||
utilityModifier !== null && utilityModifier !== void 0 ? utilityModifier : null | ||
]; | ||
} | ||
return []; | ||
} |
@@ -5,5 +5,21 @@ "use strict"; | ||
}); | ||
exports.default = _default; | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, /** | ||
* @template {string | import('postcss-selector-parser').Root} T | ||
* | ||
* Prefix all classes in the selector with the given prefix | ||
* | ||
* It can take either a string or a selector AST and will return the same type | ||
* | ||
* @param {string} prefix | ||
* @param {T} selector | ||
* @param {boolean} prependNegative | ||
* @returns {T} | ||
*/ "default", { | ||
enumerable: true, | ||
get: function() { | ||
return _default; | ||
} | ||
}); | ||
const _postcssselectorparser = /*#__PURE__*/ _interop_require_default(require("postcss-selector-parser")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -14,9 +30,12 @@ default: obj | ||
function _default(prefix, selector, prependNegative = false) { | ||
return (0, _postcssSelectorParser).default((selectors)=>{ | ||
selectors.walkClasses((classSelector)=>{ | ||
let baseClass = classSelector.value; | ||
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith("-"); | ||
classSelector.value = shouldPlaceNegativeBeforePrefix ? `-${prefix}${baseClass.slice(1)}` : `${prefix}${baseClass}`; | ||
}); | ||
}).processSync(selector); | ||
if (prefix === "") { | ||
return selector; | ||
} | ||
/** @type {import('postcss-selector-parser').Root} */ let ast = typeof selector === "string" ? (0, _postcssselectorparser.default)().astSync(selector) : selector; | ||
ast.walkClasses((classSelector)=>{ | ||
let baseClass = classSelector.value; | ||
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith("-"); | ||
classSelector.value = shouldPlaceNegativeBeforePrefix ? `-${prefix}${baseClass.slice(1)}` : `${prefix}${baseClass}`; | ||
}); | ||
return typeof selector === "string" ? ast.toString() : ast; | ||
} |
@@ -5,36 +5,21 @@ "use strict"; | ||
}); | ||
exports.default = resolveConfig; | ||
var _negateValue = _interopRequireDefault(require("./negateValue")); | ||
var _corePluginList = _interopRequireDefault(require("../corePluginList")); | ||
var _configurePlugins = _interopRequireDefault(require("./configurePlugins")); | ||
var _defaultConfigStub = _interopRequireDefault(require("../../stubs/defaultConfig.stub")); | ||
var _colors = _interopRequireDefault(require("../public/colors")); | ||
var _defaults = require("./defaults"); | ||
var _toPath = require("./toPath"); | ||
var _normalizeConfig = require("./normalizeConfig"); | ||
var _isPlainObject = _interopRequireDefault(require("./isPlainObject")); | ||
var _cloneDeep = require("./cloneDeep"); | ||
function resolveConfig(configs) { | ||
let allConfigs = [ | ||
...extractPluginConfigs(configs), | ||
{ | ||
prefix: "", | ||
important: false, | ||
separator: ":", | ||
variantOrder: _defaultConfigStub.default.variantOrder | ||
}, | ||
]; | ||
var ref, ref1; | ||
return (0, _normalizeConfig).normalizeConfig((0, _defaults).defaults({ | ||
theme: resolveFunctionKeys(mergeExtensions(mergeThemes(allConfigs.map((t)=>{ | ||
return (ref = t === null || t === void 0 ? void 0 : t.theme) !== null && ref !== void 0 ? ref : {}; | ||
})))), | ||
corePlugins: resolveCorePlugins(allConfigs.map((c)=>c.corePlugins | ||
)), | ||
plugins: resolvePluginLists(configs.map((c)=>{ | ||
return (ref1 = c === null || c === void 0 ? void 0 : c.plugins) !== null && ref1 !== void 0 ? ref1 : []; | ||
})) | ||
}, ...allConfigs)); | ||
} | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return resolveConfig; | ||
} | ||
}); | ||
const _negateValue = /*#__PURE__*/ _interop_require_default(require("./negateValue")); | ||
const _corePluginList = /*#__PURE__*/ _interop_require_default(require("../corePluginList")); | ||
const _configurePlugins = /*#__PURE__*/ _interop_require_default(require("./configurePlugins")); | ||
const _colors = /*#__PURE__*/ _interop_require_default(require("../public/colors")); | ||
const _defaults = require("./defaults"); | ||
const _toPath = require("./toPath"); | ||
const _normalizeConfig = require("./normalizeConfig"); | ||
const _isPlainObject = /*#__PURE__*/ _interop_require_default(require("./isPlainObject")); | ||
const _cloneDeep = require("./cloneDeep"); | ||
const _pluginUtils = require("./pluginUtils"); | ||
const _withAlphaVariable = require("./withAlphaVariable"); | ||
const _toColorValue = /*#__PURE__*/ _interop_require_default(require("./toColorValue")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -47,5 +32,2 @@ default: obj | ||
} | ||
function isObject(input) { | ||
return typeof input === "object" && input !== null; | ||
} | ||
function mergeWith(target, ...sources) { | ||
@@ -57,4 +39,4 @@ let customizer = sources.pop(); | ||
if (merged === undefined) { | ||
if (isObject(target[k]) && isObject(source[k])) { | ||
target[k] = mergeWith(target[k], source[k], customizer); | ||
if ((0, _isPlainObject.default)(target[k]) && (0, _isPlainObject.default)(source[k])) { | ||
target[k] = mergeWith({}, target[k], source[k], customizer); | ||
} else { | ||
@@ -74,5 +56,4 @@ target[k] = source[k]; | ||
// TODO: Log that this function isn't really needed anymore? | ||
return Object.keys(scale).filter((key)=>scale[key] !== "0" | ||
).reduce((negativeScale, key)=>{ | ||
let negativeValue = (0, _negateValue).default(scale[key]); | ||
return Object.keys(scale).filter((key)=>scale[key] !== "0").reduce((negativeScale, key)=>{ | ||
let negativeValue = (0, _negateValue.default)(scale[key]); | ||
if (negativeValue !== undefined) { | ||
@@ -85,30 +66,6 @@ negativeScale[`-${key}`] = negativeValue; | ||
breakpoints (screens) { | ||
return Object.keys(screens).filter((key)=>typeof screens[key] === "string" | ||
).reduce((breakpoints, key)=>({ | ||
return Object.keys(screens).filter((key)=>typeof screens[key] === "string").reduce((breakpoints, key)=>({ | ||
...breakpoints, | ||
[`screen-${key}`]: screens[key] | ||
}) | ||
, {}); | ||
}, | ||
rgb (property) { | ||
if (!property.startsWith("--")) { | ||
throw new Error("The rgb() helper requires a custom property name to be passed as the first argument."); | ||
} | ||
return ({ opacityValue })=>{ | ||
if (opacityValue === undefined || opacityValue === 1) { | ||
return `rgb(var(${property}) / 1.0)`; | ||
} | ||
return `rgb(var(${property}) / ${opacityValue})`; | ||
}; | ||
}, | ||
hsl (property) { | ||
if (!property.startsWith("--")) { | ||
throw new Error("The hsl() helper requires a custom property name to be passed as the first argument."); | ||
} | ||
return ({ opacityValue })=>{ | ||
if (opacityValue === undefined || opacityValue === 1) { | ||
return `hsl(var(${property}) / 1)`; | ||
} | ||
return `hsl(var(${property}) / ${opacityValue})`; | ||
}; | ||
}), {}); | ||
} | ||
@@ -142,4 +99,3 @@ }; | ||
return { | ||
...themes.reduce((merged, theme)=>(0, _defaults).defaults(merged, theme) | ||
, {}), | ||
...themes.reduce((merged, theme)=>(0, _defaults.defaults)(merged, theme), {}), | ||
// In order to resolve n config objects, we combine all of their `extend` properties | ||
@@ -150,17 +106,17 @@ // into arrays instead of objects so they aren't overridden. | ||
} | ||
function mergeExtensionCustomizer(merged, value1) { | ||
function mergeExtensionCustomizer(merged, value) { | ||
// When we have an array of objects, we do want to merge it | ||
if (Array.isArray(merged) && isObject(merged[0])) { | ||
return merged.concat(value1); | ||
if (Array.isArray(merged) && (0, _isPlainObject.default)(merged[0])) { | ||
return merged.concat(value); | ||
} | ||
// When the incoming value is an array, and the existing config is an object, prepend the existing object | ||
if (Array.isArray(value1) && isObject(value1[0]) && isObject(merged)) { | ||
if (Array.isArray(value) && (0, _isPlainObject.default)(value[0]) && (0, _isPlainObject.default)(merged)) { | ||
return [ | ||
merged, | ||
...value1 | ||
...value | ||
]; | ||
} | ||
// Override arrays (for example for font-families, box-shadows, ...) | ||
if (Array.isArray(value1)) { | ||
return value1; | ||
if (Array.isArray(value)) { | ||
return value; | ||
} | ||
@@ -179,33 +135,58 @@ // Execute default behaviour | ||
...extensions | ||
].map((e)=>value(e, resolveThemePath, utils) | ||
), mergeExtensionCustomizer) | ||
; | ||
].map((e)=>value(e, resolveThemePath, utils)), mergeExtensionCustomizer); | ||
}); | ||
} | ||
/** | ||
* | ||
* @param {string} key | ||
* @return {Iterable<string[] & {alpha: string | undefined}>} | ||
*/ function* toPaths(key) { | ||
let path = (0, _toPath.toPath)(key); | ||
if (path.length === 0) { | ||
return; | ||
} | ||
yield path; | ||
if (Array.isArray(key)) { | ||
return; | ||
} | ||
let pattern = /^(.*?)\s*\/\s*([^/]+)$/; | ||
let matches = key.match(pattern); | ||
if (matches !== null) { | ||
let [, prefix, alpha] = matches; | ||
let newPath = (0, _toPath.toPath)(prefix); | ||
newPath.alpha = alpha; | ||
yield newPath; | ||
} | ||
} | ||
function resolveFunctionKeys(object) { | ||
// theme('colors.red.500 / 0.5') -> ['colors', 'red', '500 / 0', '5] | ||
const resolvePath = (key, defaultValue)=>{ | ||
const path = (0, _toPath).toPath(key); | ||
let index = 0; | ||
let val = object; | ||
while(val !== undefined && val !== null && index < path.length){ | ||
val = val[path[index++]]; | ||
val = isFunction(val) ? val(resolvePath, configUtils) : val; | ||
for (const path of toPaths(key)){ | ||
let index = 0; | ||
let val = object; | ||
while(val !== undefined && val !== null && index < path.length){ | ||
val = val[path[index++]]; | ||
let shouldResolveAsFn = isFunction(val) && (path.alpha === undefined || index <= path.length - 1); | ||
val = shouldResolveAsFn ? val(resolvePath, configUtils) : val; | ||
} | ||
if (val !== undefined) { | ||
if (path.alpha !== undefined) { | ||
let normalized = (0, _pluginUtils.parseColorFormat)(val); | ||
return (0, _withAlphaVariable.withAlphaValue)(normalized, path.alpha, (0, _toColorValue.default)(normalized)); | ||
} | ||
if ((0, _isPlainObject.default)(val)) { | ||
return (0, _cloneDeep.cloneDeep)(val); | ||
} | ||
return val; | ||
} | ||
} | ||
if (val === undefined) { | ||
return defaultValue; | ||
} | ||
if ((0, _isPlainObject).default(val)) { | ||
return (0, _cloneDeep).cloneDeep(val); | ||
} | ||
return val; | ||
return defaultValue; | ||
}; | ||
resolvePath.theme = resolvePath; | ||
for(let key1 in configUtils){ | ||
resolvePath[key1] = configUtils[key1]; | ||
} | ||
Object.assign(resolvePath, { | ||
theme: resolvePath, | ||
...configUtils | ||
}); | ||
return Object.keys(object).reduce((resolved, key)=>{ | ||
return { | ||
...resolved, | ||
[key]: isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key] | ||
}; | ||
resolved[key] = isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key]; | ||
return resolved; | ||
}, {}); | ||
@@ -220,4 +201,4 @@ } | ||
]; | ||
var ref2; | ||
const plugins = (ref2 = config === null || config === void 0 ? void 0 : config.plugins) !== null && ref2 !== void 0 ? ref2 : []; | ||
var _config_plugins; | ||
const plugins = (_config_plugins = config === null || config === void 0 ? void 0 : config.plugins) !== null && _config_plugins !== void 0 ? _config_plugins : []; | ||
if (plugins.length === 0) { | ||
@@ -230,7 +211,7 @@ return; | ||
} | ||
var ref; | ||
var _plugin_config; | ||
allConfigs = [ | ||
...allConfigs, | ||
...extractPluginConfigs([ | ||
(ref = plugin === null || plugin === void 0 ? void 0 : plugin.config) !== null && ref !== void 0 ? ref : {} | ||
(_plugin_config = plugin === null || plugin === void 0 ? void 0 : plugin.config) !== null && _plugin_config !== void 0 ? _plugin_config : {} | ||
]) | ||
@@ -251,3 +232,3 @@ ]; | ||
} | ||
return (0, _configurePlugins).default(corePluginConfig, resolved); | ||
return (0, _configurePlugins.default)(corePluginConfig, resolved); | ||
}, _corePluginList.default); | ||
@@ -267,1 +248,21 @@ return result; | ||
} | ||
function resolveConfig(configs) { | ||
let allConfigs = [ | ||
...extractPluginConfigs(configs), | ||
{ | ||
prefix: "", | ||
important: false, | ||
separator: ":" | ||
} | ||
]; | ||
var _t_theme, _c_plugins; | ||
return (0, _normalizeConfig.normalizeConfig)((0, _defaults.defaults)({ | ||
theme: resolveFunctionKeys(mergeExtensions(mergeThemes(allConfigs.map((t)=>{ | ||
return (_t_theme = t === null || t === void 0 ? void 0 : t.theme) !== null && _t_theme !== void 0 ? _t_theme : {}; | ||
})))), | ||
corePlugins: resolveCorePlugins(allConfigs.map((c)=>c.corePlugins)), | ||
plugins: resolvePluginLists(configs.map((c)=>{ | ||
return (_c_plugins = c === null || c === void 0 ? void 0 : c.plugins) !== null && _c_plugins !== void 0 ? _c_plugins : []; | ||
})) | ||
}, ...allConfigs)); | ||
} |
@@ -5,5 +5,40 @@ "use strict"; | ||
}); | ||
exports.default = resolveConfigPath; | ||
var _fs = _interopRequireDefault(require("fs")); | ||
var _path = _interopRequireDefault(require("path")); | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
default: function() { | ||
return resolveConfigPath; | ||
}, | ||
resolveDefaultConfigPath: function() { | ||
return resolveDefaultConfigPath; | ||
} | ||
}); | ||
const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); | ||
const _path = /*#__PURE__*/ _interop_require_default(require("path")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
const defaultConfigFiles = [ | ||
"./tailwind.config.js", | ||
"./tailwind.config.cjs", | ||
"./tailwind.config.mjs", | ||
"./tailwind.config.ts", | ||
"./tailwind.config.cts", | ||
"./tailwind.config.mts" | ||
]; | ||
function isObject(value) { | ||
return typeof value === "object" && value !== null; | ||
} | ||
function isEmpty(obj) { | ||
return Object.keys(obj).length === 0; | ||
} | ||
function isString(value) { | ||
return typeof value === "string" || value instanceof String; | ||
} | ||
function resolveConfigPath(pathOrConfig) { | ||
@@ -27,6 +62,6 @@ // require('tailwindcss')({ theme: ..., variants: ... }) | ||
// require('tailwindcss') | ||
for (const configFile of [ | ||
"./tailwind.config.js", | ||
"./tailwind.config.cjs" | ||
]){ | ||
return resolveDefaultConfigPath(); | ||
} | ||
function resolveDefaultConfigPath() { | ||
for (const configFile of defaultConfigFiles){ | ||
try { | ||
@@ -40,15 +75,1 @@ const configPath = _path.default.resolve(configFile); | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function isObject(value) { | ||
return typeof value === "object" && value !== null; | ||
} | ||
function isEmpty(obj) { | ||
return Object.keys(obj).length === 0; | ||
} | ||
function isString(value) { | ||
return typeof value === "string" || value instanceof String; | ||
} |
@@ -5,16 +5,21 @@ "use strict"; | ||
}); | ||
exports.default = responsive; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
var _cloneNodes = _interopRequireDefault(require("./cloneNodes")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return responsive; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _cloneNodes = /*#__PURE__*/ _interop_require_default(require("./cloneNodes")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function responsive(rules) { | ||
return _postcss.default.atRule({ | ||
name: "responsive" | ||
}).append((0, _cloneNodes).default(Array.isArray(rules) ? rules : [ | ||
}).append((0, _cloneNodes.default)(Array.isArray(rules) ? rules : [ | ||
rules | ||
])); | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -1,72 +0,47 @@ | ||
"use strict"; | ||
/** | ||
* This splits a string on a top-level character. | ||
* | ||
* Regex doesn't support recursion (at least not the JS-flavored version). | ||
* So we have to use a tiny state machine to keep track of paren placement. | ||
* | ||
* Expected behavior using commas: | ||
* var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0) | ||
* ─┬─ ┬ ┬ ┬ | ||
* x x x ╰──────── Split because top-level | ||
* ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens | ||
* | ||
* @param {string} input | ||
* @param {string} separator | ||
*/ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.splitAtTopLevelOnly = splitAtTopLevelOnly; | ||
var regex = _interopRequireWildcard(require("../lib/regex")); | ||
function _interopRequireWildcard(obj) { | ||
if (obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = {}; | ||
if (obj != null) { | ||
for(var key in obj){ | ||
if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; | ||
if (desc.get || desc.set) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
} | ||
} | ||
} | ||
newObj.default = obj; | ||
return newObj; | ||
Object.defineProperty(exports, "splitAtTopLevelOnly", { | ||
enumerable: true, | ||
get: function() { | ||
return splitAtTopLevelOnly; | ||
} | ||
} | ||
function* splitAtTopLevelOnly(input, separator) { | ||
let SPECIALS = new RegExp(`[(){}\\[\\]${regex.escape(separator)}]`, "g"); | ||
let depth = 0; | ||
let lastIndex = 0; | ||
let found = false; | ||
let separatorIndex = 0; | ||
let separatorStart = 0; | ||
let separatorLength = separator.length; | ||
// Find all paren-like things & character | ||
// And only split on commas if they're top-level | ||
for (let match of input.matchAll(SPECIALS)){ | ||
let matchesSeparator = match[0] === separator[separatorIndex]; | ||
let atEndOfSeparator = separatorIndex === separatorLength - 1; | ||
let matchesFullSeparator = matchesSeparator && atEndOfSeparator; | ||
if (match[0] === "(") depth++; | ||
if (match[0] === ")") depth--; | ||
if (match[0] === "[") depth++; | ||
if (match[0] === "]") depth--; | ||
if (match[0] === "{") depth++; | ||
if (match[0] === "}") depth--; | ||
if (matchesSeparator && depth === 0) { | ||
if (separatorStart === 0) { | ||
separatorStart = match.index; | ||
}); | ||
function splitAtTopLevelOnly(input, separator) { | ||
let stack = []; | ||
let parts = []; | ||
let lastPos = 0; | ||
let isEscaped = false; | ||
for(let idx = 0; idx < input.length; idx++){ | ||
let char = input[idx]; | ||
if (stack.length === 0 && char === separator[0] && !isEscaped) { | ||
if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) { | ||
parts.push(input.slice(lastPos, idx)); | ||
lastPos = idx + separator.length; | ||
} | ||
separatorIndex++; | ||
} | ||
if (matchesFullSeparator && depth === 0) { | ||
found = true; | ||
yield input.substring(lastIndex, separatorStart); | ||
lastIndex = separatorStart + separatorLength; | ||
isEscaped = isEscaped ? false : char === "\\"; | ||
if (char === "(" || char === "[" || char === "{") { | ||
stack.push(char); | ||
} else if (char === ")" && stack[stack.length - 1] === "(" || char === "]" && stack[stack.length - 1] === "[" || char === "}" && stack[stack.length - 1] === "{") { | ||
stack.pop(); | ||
} | ||
if (separatorIndex === separatorLength) { | ||
separatorIndex = 0; | ||
separatorStart = 0; | ||
} | ||
} | ||
// Provide the last segment of the string if available | ||
// Otherwise the whole string since no `char`s were found | ||
// This mirrors the behavior of string.split() | ||
if (found) { | ||
yield input.substring(lastIndex); | ||
} else { | ||
yield input; | ||
} | ||
parts.push(input.slice(lastPos)); | ||
return parts; | ||
} |
@@ -5,3 +5,8 @@ "use strict"; | ||
}); | ||
exports.tap = tap; | ||
Object.defineProperty(exports, "tap", { | ||
enumerable: true, | ||
get: function() { | ||
return tap; | ||
} | ||
}); | ||
function tap(value, mutator) { | ||
@@ -8,0 +13,0 @@ mutator(value); |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.default = toColorValue; | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return toColorValue; | ||
} | ||
}); | ||
function toColorValue(maybeFunction) { | ||
return typeof maybeFunction === "function" ? maybeFunction({}) : maybeFunction; | ||
} |
@@ -1,6 +0,24 @@ | ||
"use strict"; | ||
/** | ||
* Parse a path string into an array of path segments. | ||
* | ||
* Square bracket notation `a[b]` may be used to "escape" dots that would otherwise be interpreted as path separators. | ||
* | ||
* Example: | ||
* a -> ['a'] | ||
* a.b.c -> ['a', 'b', 'c'] | ||
* a[b].c -> ['a', 'b', 'c'] | ||
* a[b.c].e.f -> ['a', 'b.c', 'e', 'f'] | ||
* a[b][c][d] -> ['a', 'b', 'c', 'd'] | ||
* | ||
* @param {string|string[]} path | ||
**/ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.toPath = toPath; | ||
Object.defineProperty(exports, "toPath", { | ||
enumerable: true, | ||
get: function() { | ||
return toPath; | ||
} | ||
}); | ||
function toPath(path) { | ||
@@ -7,0 +25,0 @@ if (Array.isArray(path)) return path; |
@@ -5,4 +5,15 @@ "use strict"; | ||
}); | ||
exports.default = transformThemeValue; | ||
var _postcss = _interopRequireDefault(require("postcss")); | ||
Object.defineProperty(exports, "default", { | ||
enumerable: true, | ||
get: function() { | ||
return transformThemeValue; | ||
} | ||
}); | ||
const _postcss = /*#__PURE__*/ _interop_require_default(require("postcss")); | ||
const _isPlainObject = /*#__PURE__*/ _interop_require_default(require("./isPlainObject")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} | ||
function transformThemeValue(themeSection) { | ||
@@ -19,4 +30,10 @@ if ([ | ||
} | ||
if (themeSection === "fontFamily") { | ||
return (value)=>{ | ||
if (typeof value === "function") value = value({}); | ||
let families = Array.isArray(value) && (0, _isPlainObject.default)(value[1]) ? value[0] : value; | ||
return Array.isArray(families) ? families.join(", ") : families; | ||
}; | ||
} | ||
if ([ | ||
"fontFamily", | ||
"boxShadow", | ||
@@ -31,3 +48,3 @@ "transitionProperty", | ||
"cursor", | ||
"animation", | ||
"animation" | ||
].includes(themeSection)) { | ||
@@ -53,11 +70,8 @@ return (value)=>{ | ||
} | ||
return (value)=>{ | ||
if (typeof value === "function") value = value({}); | ||
return (value, opts = {})=>{ | ||
if (typeof value === "function") { | ||
value = value(opts); | ||
} | ||
return value; | ||
}; | ||
} | ||
function _interopRequireDefault(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
@@ -5,5 +5,10 @@ "use strict"; | ||
}); | ||
exports.validateConfig = validateConfig; | ||
var _log = _interopRequireDefault(require("./log")); | ||
function _interopRequireDefault(obj) { | ||
Object.defineProperty(exports, "validateConfig", { | ||
enumerable: true, | ||
get: function() { | ||
return validateConfig; | ||
} | ||
}); | ||
const _log = /*#__PURE__*/ _interop_require_default(require("./log")); | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
@@ -18,6 +23,17 @@ default: obj | ||
"Configure your content sources or your generated CSS will be missing styles.", | ||
"https://tailwindcss.com/docs/content-configuration", | ||
"https://tailwindcss.com/docs/content-configuration" | ||
]); | ||
} | ||
// Warn if the line-clamp plugin is installed | ||
try { | ||
let plugin = require("@tailwindcss/line-clamp"); | ||
if (config.plugins.includes(plugin)) { | ||
_log.default.warn("line-clamp-in-core", [ | ||
"As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.", | ||
"Remove it from the `plugins` array in your configuration to eliminate this warning." | ||
]); | ||
config.plugins = config.plugins.filter((p)=>p !== plugin); | ||
} | ||
} catch {} | ||
return config; | ||
} |
@@ -5,5 +5,34 @@ "use strict"; | ||
}); | ||
exports.default = withAlphaVariable; | ||
exports.withAlphaValue = withAlphaValue; | ||
var _color = require("./color"); | ||
function _export(target, all) { | ||
for(var name in all)Object.defineProperty(target, name, { | ||
enumerable: true, | ||
get: all[name] | ||
}); | ||
} | ||
_export(exports, { | ||
withAlphaValue: function() { | ||
return withAlphaValue; | ||
}, | ||
default: function() { | ||
return withAlphaVariable; | ||
} | ||
}); | ||
const _color = require("./color"); | ||
function withAlphaValue(color, alphaValue, defaultValue) { | ||
if (typeof color === "function") { | ||
return color({ | ||
opacityValue: alphaValue | ||
}); | ||
} | ||
let parsed = (0, _color.parseColor)(color, { | ||
loose: true | ||
}); | ||
if (parsed === null) { | ||
return defaultValue; | ||
} | ||
return (0, _color.formatColor)({ | ||
...parsed, | ||
alpha: alphaValue | ||
}); | ||
} | ||
function withAlphaVariable({ color , property , variable }) { | ||
@@ -25,3 +54,3 @@ let properties = [].concat(property); | ||
} | ||
const parsed = (0, _color).parseColor(color); | ||
const parsed = (0, _color.parseColor)(color); | ||
if (parsed === null) { | ||
@@ -31,4 +60,3 @@ return Object.fromEntries(properties.map((p)=>[ | ||
color | ||
] | ||
)); | ||
])); | ||
} | ||
@@ -40,4 +68,3 @@ if (parsed.alpha !== undefined) { | ||
color | ||
] | ||
)); | ||
])); | ||
} | ||
@@ -49,3 +76,3 @@ return { | ||
p, | ||
(0, _color).formatColor({ | ||
(0, _color.formatColor)({ | ||
...parsed, | ||
@@ -58,16 +85,1 @@ alpha: `var(${variable})` | ||
} | ||
function withAlphaValue(color, alphaValue, defaultValue) { | ||
if (typeof color === "function") { | ||
return color({ | ||
opacityValue: alphaValue | ||
}); | ||
} | ||
let parsed = (0, _color).parseColor(color); | ||
if (parsed === null) { | ||
return defaultValue; | ||
} | ||
return (0, _color).formatColor({ | ||
...parsed, | ||
alpha: alphaValue | ||
}); | ||
} |
{ | ||
"name": "tailwindcss", | ||
"version": "0.0.0-insiders.23d3a31", | ||
"version": "0.0.0-insiders.245058c", | ||
"description": "A utility-first CSS framework for rapidly building custom user interfaces.", | ||
@@ -16,7 +16,6 @@ "license": "MIT", | ||
"scripts": { | ||
"preswcify": "npm run generate && rimraf lib", | ||
"swcify": "swc src --out-dir lib --copy-files", | ||
"postswcify": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js", | ||
"rebuild-fixtures": "npm run swcify && node -r @swc/register scripts/rebuildFixtures.js", | ||
"prepublishOnly": "npm install --force && npm run swcify", | ||
"prebuild": "npm run generate && rimraf lib", | ||
"build": "swc src --out-dir lib --copy-files", | ||
"postbuild": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js --define:process.env.CSS_TRANSFORMER_WASM=false", | ||
"rebuild-fixtures": "npm run build && node -r @swc/register scripts/rebuildFixtures.js", | ||
"style": "eslint .", | ||
@@ -27,6 +26,8 @@ "pretest": "npm run generate", | ||
"install:integrations": "node scripts/install-integrations.js", | ||
"posttest": "npm run style", | ||
"generate:plugin-list": "node -r @swc/register scripts/create-plugin-list.js", | ||
"generate:types": "node -r @swc/register scripts/generate-types.js", | ||
"generate": "npm run generate:plugin-list && npm run generate:types" | ||
"generate": "npm run generate:plugin-list && npm run generate:types", | ||
"release-channel": "node ./scripts/release-channel.js", | ||
"release-notes": "node ./scripts/release-notes.js", | ||
"prepublishOnly": "npm install --force && npm run build" | ||
}, | ||
@@ -39,3 +40,3 @@ "files": [ | ||
"scripts/*.js", | ||
"stubs/*.stub.js", | ||
"stubs/*", | ||
"nesting/*", | ||
@@ -48,44 +49,45 @@ "types/**/*", | ||
"devDependencies": { | ||
"@swc/cli": "^0.1.57", | ||
"@swc/core": "^1.2.160", | ||
"@swc/jest": "^0.2.21", | ||
"@swc/cli": "^0.1.62", | ||
"@swc/core": "^1.3.55", | ||
"@swc/jest": "^0.2.26", | ||
"@swc/register": "^0.1.10", | ||
"autoprefixer": "^10.4.5", | ||
"cssnano": "^5.1.7", | ||
"esbuild": "^0.14.39", | ||
"eslint": "^8.15.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"jest": "^28.0.3", | ||
"jest-diff": "^28.0.2", | ||
"prettier": "^2.6.2", | ||
"prettier-plugin-tailwindcss": "^0.1.10", | ||
"rimraf": "^3.0.0", | ||
"source-map-js": "^1.0.2" | ||
"autoprefixer": "^10.4.14", | ||
"browserslist": "^4.21.5", | ||
"concurrently": "^8.0.1", | ||
"cssnano": "^6.0.0", | ||
"esbuild": "^0.20.2", | ||
"eslint": "^8.39.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"jest": "^29.6.0", | ||
"jest-diff": "^29.6.0", | ||
"lightningcss": "1.24.1", | ||
"prettier": "^2.8.8", | ||
"rimraf": "^5.0.0", | ||
"source-map-js": "^1.0.2", | ||
"turbo": "^1.9.3" | ||
}, | ||
"peerDependencies": { | ||
"postcss": "^8.0.9" | ||
}, | ||
"dependencies": { | ||
"arg": "^5.0.1", | ||
"@alloc/quick-lru": "^5.2.0", | ||
"arg": "^5.0.2", | ||
"chokidar": "^3.5.3", | ||
"color-name": "^1.1.4", | ||
"detective": "^5.2.0", | ||
"didyoumean": "^1.2.2", | ||
"dlv": "^1.1.3", | ||
"fast-glob": "^3.2.11", | ||
"fast-glob": "^3.3.0", | ||
"glob-parent": "^6.0.2", | ||
"is-glob": "^4.0.3", | ||
"lilconfig": "^2.0.5", | ||
"jiti": "^1.21.0", | ||
"lilconfig": "^2.1.0", | ||
"micromatch": "^4.0.5", | ||
"normalize-path": "^3.0.0", | ||
"object-hash": "^3.0.0", | ||
"picocolors": "^1.0.0", | ||
"postcss": "^8.4.13", | ||
"postcss-js": "^4.0.0", | ||
"postcss-load-config": "^3.1.4", | ||
"postcss-nested": "5.0.6", | ||
"postcss-selector-parser": "^6.0.10", | ||
"postcss-value-parser": "^4.2.0", | ||
"quick-lru": "^5.1.1", | ||
"resolve": "^1.22.0" | ||
"postcss": "^8.4.23", | ||
"postcss-import": "^15.1.0", | ||
"postcss-js": "^4.0.1", | ||
"postcss-load-config": "^4.0.1", | ||
"postcss-nested": "^6.0.1", | ||
"postcss-selector-parser": "^6.0.11", | ||
"resolve": "^1.22.2", | ||
"sucrase": "^3.32.0" | ||
}, | ||
@@ -106,11 +108,16 @@ "browserslist": [ | ||
"/integrations/", | ||
"/standalone-cli/" | ||
"/standalone-cli/", | ||
"\\.test\\.skip\\.js$" | ||
], | ||
"transformIgnorePatterns": [ | ||
"node_modules/(?!lightningcss)" | ||
], | ||
"transform": { | ||
"\\.js$": "@swc/jest" | ||
"\\.js$": "@swc/jest", | ||
"\\.ts$": "@swc/jest" | ||
} | ||
}, | ||
"engines": { | ||
"node": ">=12.13.0" | ||
"node": ">=14.0.0" | ||
} | ||
} |
import type { Config, PluginCreator } from './types/config' | ||
declare function createPlugin( | ||
plugin: PluginCreator, | ||
config?: Config | ||
): { handler: PluginCreator; config?: Config } | ||
export = createPlugin | ||
type Plugin = { | ||
withOptions<T>( | ||
plugin: (options: T) => PluginCreator, | ||
config?: (options: T) => Partial<Config> | ||
): { (options: T): { handler: PluginCreator; config?: Partial<Config> }; __isOptionsFunction: true } | ||
(plugin: PluginCreator, config?: Partial<Config>): { handler: PluginCreator; config?: Partial<Config> } | ||
} | ||
declare const plugin: Plugin | ||
export = plugin |
@@ -1,14 +0,17 @@ | ||
<p> | ||
<a href="https://tailwindcss.com/#gh-light-mode-only" target="_blank"> | ||
<img src="./.github/logo-light.svg" alt="Tailwind CSS" width="350" height="70"> | ||
<p align="center"> | ||
<a href="https://tailwindcss.com" target="_blank"> | ||
<picture> | ||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-dark.svg"> | ||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg"> | ||
<img alt="Tailwind CSS" src="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg" width="350" height="70" style="max-width: 100%;"> | ||
</picture> | ||
</a> | ||
<a href="https://tailwindcss.com/#gh-dark-mode-only" target="_blank"> | ||
<img src="./.github/logo-dark.svg" alt="Tailwind CSS" width="350" height="70"> | ||
</a> | ||
</p> | ||
A utility-first CSS framework for rapidly building custom user interfaces. | ||
<p align="center"> | ||
A utility-first CSS framework for rapidly building custom user interfaces. | ||
</p> | ||
<p> | ||
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/workflow/status/tailwindlabs/tailwindcss/Node.js%20CI" alt="Build Status"></a> | ||
<p align="center"> | ||
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=master" alt="Build Status"></a> | ||
<a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a> | ||
@@ -19,3 +22,3 @@ <a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a> | ||
------ | ||
--- | ||
@@ -22,0 +25,0 @@ ## Documentation |
import prettier from 'prettier' | ||
import { corePlugins } from '../src/corePlugins' | ||
import colors from '../src/public/colors' | ||
import defaultTheme from '../src/public/default-theme' | ||
import fs from 'fs' | ||
import path from 'path' | ||
import * as types from './type-utils' | ||
@@ -53,1 +55,51 @@ fs.writeFileSync( | ||
) | ||
const defaultThemeTypes = Object.entries(defaultTheme) | ||
.map(([name, value]) => { | ||
// Special cases for slightly more accurate types | ||
if (name === 'keyframes') { | ||
return [name, `Record<${types.forKeys(value)}, Record<string, CSSDeclarationList>>`] | ||
} | ||
if (name === 'fontSize') { | ||
return [name, `Record<${types.forKeys(value)}, [string, { lineHeight: string }]>`] | ||
} | ||
// General cases | ||
if (typeof value === 'string') { | ||
return [name, `string`] | ||
} | ||
if (typeof value === 'function') { | ||
return [name, null] | ||
} | ||
if (typeof value === 'object') { | ||
if (Object.keys(value).length === 0) { | ||
return [name, null] | ||
} | ||
return [name, types.forValue(value)] | ||
} | ||
return [name, `unknown`] | ||
}) | ||
.filter(([, type]) => type !== null) | ||
.map(([name, type]) => `${name}: ${type}`) | ||
.join('\n') | ||
fs.writeFileSync( | ||
path.join(process.cwd(), 'types', 'generated', 'default-theme.d.ts'), | ||
prettier.format( | ||
` | ||
type CSSDeclarationList = Record<string, string> | ||
export type DefaultTheme = { ${defaultThemeTypes} } | ||
`, | ||
{ | ||
semi: false, | ||
singleQuote: true, | ||
printWidth: 100, | ||
parser: 'typescript', | ||
} | ||
) | ||
) |
@@ -5,2 +5,6 @@ export function lazyPostcss() { | ||
export function lazyPostcssImport() { | ||
return require('postcss-import') | ||
} | ||
export function lazyAutoprefixer() { | ||
@@ -7,0 +11,0 @@ return require('autoprefixer') |
882
src/cli.js
#!/usr/bin/env node | ||
import { lazyPostcss, lazyCssnano, lazyAutoprefixer } from '../peers/index.js' | ||
import chokidar from 'chokidar' | ||
import path from 'path' | ||
import arg from 'arg' | ||
import fs from 'fs' | ||
import postcssrc from 'postcss-load-config' | ||
import { lilconfig } from 'lilconfig' | ||
import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API | ||
import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API | ||
import tailwind from './processTailwindFeatures' | ||
import resolveConfigInternal from '../resolveConfig' | ||
import fastGlob from 'fast-glob' | ||
import getModuleDependencies from './lib/getModuleDependencies' | ||
import log from './util/log' | ||
import packageJson from '../package.json' | ||
import normalizePath from 'normalize-path' | ||
import { validateConfig } from './util/validateConfig.js' | ||
let env = { | ||
DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== '0', | ||
} | ||
function isESM() { | ||
const pkgPath = path.resolve('./package.json') | ||
try { | ||
let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) | ||
return pkg.type && pkg.type === 'module' | ||
} catch (err) { | ||
return false | ||
} | ||
} | ||
let configs = isESM() | ||
? { | ||
tailwind: 'tailwind.config.cjs', | ||
postcss: 'postcss.config.cjs', | ||
} | ||
: { | ||
tailwind: 'tailwind.config.js', | ||
postcss: 'postcss.config.js', | ||
} | ||
// --- | ||
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 = '' | ||
} | ||
} | ||
async function outputFile(file, contents) { | ||
if (fs.existsSync(file) && (await fs.promises.readFile(file, 'utf8')) === contents) { | ||
return // Skip writing the file | ||
} | ||
// Write the file | ||
await fs.promises.writeFile(file, contents, 'utf8') | ||
} | ||
function drainStdin() { | ||
return new Promise((resolve, reject) => { | ||
let result = '' | ||
process.stdin.on('data', (chunk) => { | ||
result += chunk | ||
}) | ||
process.stdin.on('end', () => resolve(result)) | ||
process.stdin.on('error', (err) => reject(err)) | ||
}) | ||
} | ||
function help({ message, usage, commands, options }) { | ||
let indent = 2 | ||
// Render header | ||
console.log() | ||
console.log(`${packageJson.name} v${packageJson.version}`) | ||
// Render message | ||
if (message) { | ||
console.log() | ||
for (let msg of message.split('\n')) { | ||
console.log(msg) | ||
} | ||
} | ||
// Render usage | ||
if (usage && usage.length > 0) { | ||
console.log() | ||
console.log('Usage:') | ||
for (let example of usage) { | ||
console.log(' '.repeat(indent), example) | ||
} | ||
} | ||
// Render commands | ||
if (commands && commands.length > 0) { | ||
console.log() | ||
console.log('Commands:') | ||
for (let command of commands) { | ||
console.log(' '.repeat(indent), 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, deprecated } of Object.values(groupedOptions)) { | ||
if (deprecated) continue | ||
if (flags.length === 1) { | ||
console.log( | ||
' '.repeat(indent + 4 /* 4 = "-i, ".length */), | ||
flags.slice().reverse().join(', ').padEnd(20, ' '), | ||
description | ||
) | ||
} else { | ||
console.log( | ||
' '.repeat(indent), | ||
flags.slice().reverse().join(', ').padEnd(24, ' '), | ||
description | ||
) | ||
} | ||
} | ||
} | ||
console.log() | ||
} | ||
function oneOf(...options) { | ||
return Object.assign( | ||
(value = true) => { | ||
for (let option of options) { | ||
let parsed = option(value) | ||
if (parsed === value) { | ||
return parsed | ||
} | ||
} | ||
throw new Error('...') | ||
}, | ||
{ manualParsing: true } | ||
) | ||
} | ||
function loadPostcss() { | ||
// Try to load a local `postcss` version first | ||
try { | ||
return require('postcss') | ||
} catch {} | ||
return lazyPostcss() | ||
} | ||
let commands = { | ||
init: { | ||
run: init, | ||
args: { | ||
'--full': { type: Boolean, description: `Initialize a full \`${configs.tailwind}\` file` }, | ||
'--postcss': { type: Boolean, description: `Initialize a \`${configs.postcss}\` file` }, | ||
'--types': { | ||
type: Boolean, | ||
description: `Add TypeScript types for the \`${configs.tailwind}\` file`, | ||
}, | ||
'-f': '--full', | ||
'-p': '--postcss', | ||
}, | ||
}, | ||
build: { | ||
run: build, | ||
args: { | ||
'--input': { type: String, description: 'Input file' }, | ||
'--output': { type: String, description: 'Output file' }, | ||
'--watch': { type: Boolean, description: 'Watch for changes and rebuild as needed' }, | ||
'--poll': { | ||
type: Boolean, | ||
description: 'Use polling instead of filesystem events when watching', | ||
}, | ||
'--content': { | ||
type: String, | ||
description: 'Content paths to use for removing unused classes', | ||
}, | ||
'--purge': { | ||
type: String, | ||
deprecated: true, | ||
}, | ||
'--postcss': { | ||
type: oneOf(String, Boolean), | ||
description: 'Load custom PostCSS configuration', | ||
}, | ||
'--minify': { type: Boolean, description: 'Minify the output' }, | ||
'--config': { | ||
type: String, | ||
description: 'Path to a custom config file', | ||
}, | ||
'--no-autoprefixer': { | ||
type: Boolean, | ||
description: 'Disable autoprefixer', | ||
}, | ||
'-c': '--config', | ||
'-i': '--input', | ||
'-o': '--output', | ||
'-m': '--minify', | ||
'-w': '--watch', | ||
'-p': '--poll', | ||
}, | ||
}, | ||
} | ||
let sharedFlags = { | ||
'--help': { type: Boolean, description: 'Display usage information' }, | ||
'-h': '--help', | ||
} | ||
if ( | ||
process.stdout.isTTY /* Detect redirecting output to a file */ && | ||
(process.argv[2] === undefined || | ||
process.argv.slice(2).every((flag) => sharedFlags[flag] !== undefined)) | ||
) { | ||
help({ | ||
usage: [ | ||
'tailwindcss [--input input.css] [--output output.css] [--watch] [options...]', | ||
'tailwindcss init [--full] [--postcss] [--types] [options...]', | ||
], | ||
commands: Object.keys(commands) | ||
.filter((command) => command !== 'build') | ||
.map((command) => `${command} [options]`), | ||
options: { ...commands.build.args, ...sharedFlags }, | ||
}) | ||
process.exit(0) | ||
} | ||
let command = ((arg = '') => (arg.startsWith('-') ? undefined : arg))(process.argv[2]) || 'build' | ||
if (commands[command] === undefined) { | ||
if (fs.existsSync(path.resolve(command))) { | ||
// TODO: Deprecate this in future versions | ||
// Check if non-existing command, might be a file. | ||
command = 'build' | ||
} else { | ||
help({ | ||
message: `Invalid command: ${command}`, | ||
usage: ['tailwindcss <command> [options]'], | ||
commands: Object.keys(commands) | ||
.filter((command) => command !== 'build') | ||
.map((command) => `${command} [options]`), | ||
options: sharedFlags, | ||
}) | ||
process.exit(1) | ||
} | ||
} | ||
// Execute command | ||
let { args: flags, run } = commands[command] | ||
let args = (() => { | ||
try { | ||
let result = arg( | ||
Object.fromEntries( | ||
Object.entries({ ...flags, ...sharedFlags }) | ||
.filter(([_key, value]) => !value?.type?.manualParsing) | ||
.map(([key, value]) => [key, typeof value === 'object' ? value.type : value]) | ||
), | ||
{ permissive: true } | ||
) | ||
// Manual parsing of flags to allow for special flags like oneOf(Boolean, String) | ||
for (let i = result['_'].length - 1; i >= 0; --i) { | ||
let flag = result['_'][i] | ||
if (!flag.startsWith('-')) continue | ||
let flagName = flag | ||
let handler = flags[flag] | ||
// Resolve flagName & handler | ||
while (typeof handler === 'string') { | ||
flagName = handler | ||
handler = flags[handler] | ||
} | ||
if (!handler) continue | ||
let args = [] | ||
let offset = i + 1 | ||
// Parse args for current flag | ||
while (result['_'][offset] && !result['_'][offset].startsWith('-')) { | ||
args.push(result['_'][offset++]) | ||
} | ||
// Cleanup manually parsed flags + args | ||
result['_'].splice(i, 1 + args.length) | ||
// Set the resolved value in the `result` object | ||
result[flagName] = handler.type( | ||
args.length === 0 ? undefined : args.length === 1 ? args[0] : args, | ||
flagName | ||
) | ||
} | ||
// Ensure that the `command` is always the first argument in the `args`. | ||
// This is important so that we don't have to check if a default command | ||
// (build) was used or not from within each plugin. | ||
// | ||
// E.g.: tailwindcss input.css -> _: ['build', 'input.css'] | ||
// E.g.: tailwindcss build input.css -> _: ['build', 'input.css'] | ||
if (result['_'][0] !== command) { | ||
result['_'].unshift(command) | ||
} | ||
return result | ||
} catch (err) { | ||
if (err.code === 'ARG_UNKNOWN_OPTION') { | ||
help({ | ||
message: err.message, | ||
usage: ['tailwindcss <command> [options]'], | ||
options: sharedFlags, | ||
}) | ||
process.exit(1) | ||
} | ||
throw err | ||
} | ||
})() | ||
if (args['--help']) { | ||
help({ | ||
options: { ...flags, ...sharedFlags }, | ||
usage: [`tailwindcss ${command} [options]`], | ||
}) | ||
process.exit(0) | ||
} | ||
run() | ||
// --- | ||
function init() { | ||
let messages = [] | ||
let tailwindConfigLocation = path.resolve(args['_'][1] ?? `./${configs.tailwind}`) | ||
if (fs.existsSync(tailwindConfigLocation)) { | ||
messages.push(`${path.basename(tailwindConfigLocation)} already exists.`) | ||
} else { | ||
let stubFile = fs.readFileSync( | ||
args['--full'] | ||
? path.resolve(__dirname, '../stubs/defaultConfig.stub.js') | ||
: path.resolve(__dirname, '../stubs/simpleConfig.stub.js'), | ||
'utf8' | ||
) | ||
if (args['--types']) { | ||
let typesHeading = "/** @type {import('tailwindcss/types').Config} */" | ||
stubFile = | ||
stubFile.replace(`module.exports = `, `${typesHeading}\nconst config = `) + | ||
'\nmodule.exports = config' | ||
} | ||
// Change colors import | ||
stubFile = stubFile.replace('../colors', 'tailwindcss/colors') | ||
fs.writeFileSync(tailwindConfigLocation, stubFile, 'utf8') | ||
messages.push(`Created Tailwind CSS config file: ${path.basename(tailwindConfigLocation)}`) | ||
} | ||
if (args['--postcss']) { | ||
let postcssConfigLocation = path.resolve(`./${configs.postcss}`) | ||
if (fs.existsSync(postcssConfigLocation)) { | ||
messages.push(`${path.basename(postcssConfigLocation)} already exists.`) | ||
} else { | ||
let stubFile = fs.readFileSync( | ||
path.resolve(__dirname, '../stubs/defaultPostCssConfig.stub.js'), | ||
'utf8' | ||
) | ||
fs.writeFileSync(postcssConfigLocation, stubFile, 'utf8') | ||
messages.push(`Created PostCSS config file: ${path.basename(postcssConfigLocation)}`) | ||
} | ||
} | ||
if (messages.length > 0) { | ||
console.log() | ||
for (let message of messages) { | ||
console.log(message) | ||
} | ||
} | ||
} | ||
async function build() { | ||
let input = args['--input'] | ||
let output = args['--output'] | ||
let shouldWatch = args['--watch'] | ||
let shouldPoll = args['--poll'] | ||
let shouldCoalesceWriteEvents = shouldPoll || process.platform === 'win32' | ||
let includePostCss = args['--postcss'] | ||
// Polling interval in milliseconds | ||
// Used only when polling or coalescing add/change events on Windows | ||
let pollInterval = 10 | ||
// TODO: Deprecate this in future versions | ||
if (!input && args['_'][1]) { | ||
console.error('[deprecation] Running tailwindcss without -i, please provide an input file.') | ||
input = args['--input'] = args['_'][1] | ||
} | ||
if (input && input !== '-' && !fs.existsSync((input = path.resolve(input)))) { | ||
console.error(`Specified input file ${args['--input']} does not exist.`) | ||
process.exit(9) | ||
} | ||
if (args['--config'] && !fs.existsSync((args['--config'] = path.resolve(args['--config'])))) { | ||
console.error(`Specified config file ${args['--config']} does not exist.`) | ||
process.exit(9) | ||
} | ||
let configPath = args['--config'] | ||
? args['--config'] | ||
: ((defaultPath) => (fs.existsSync(defaultPath) ? defaultPath : null))( | ||
path.resolve(`./${configs.tailwind}`) | ||
) | ||
async function loadPostCssPlugins() { | ||
let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined | ||
let config = customPostCssPath | ||
? await (async () => { | ||
let file = path.resolve(customPostCssPath) | ||
// Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js | ||
let { config = {} } = await lilconfig('postcss').load(file) | ||
if (typeof config === 'function') { | ||
config = config() | ||
} else { | ||
config = Object.assign({}, config) | ||
} | ||
if (!config.plugins) { | ||
config.plugins = [] | ||
} | ||
return { | ||
file, | ||
plugins: loadPlugins(config, file), | ||
options: loadOptions(config, file), | ||
} | ||
})() | ||
: await postcssrc() | ||
let configPlugins = config.plugins | ||
let configPluginTailwindIdx = configPlugins.findIndex((plugin) => { | ||
if (typeof plugin === 'function' && plugin.name === 'tailwindcss') { | ||
return true | ||
} | ||
if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') { | ||
return true | ||
} | ||
return false | ||
}) | ||
let beforePlugins = | ||
configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx) | ||
let afterPlugins = | ||
configPluginTailwindIdx === -1 | ||
? configPlugins | ||
: configPlugins.slice(configPluginTailwindIdx + 1) | ||
return [beforePlugins, afterPlugins, config.options] | ||
} | ||
function resolveConfig() { | ||
let config = configPath ? require(configPath) : {} | ||
if (args['--purge']) { | ||
log.warn('purge-flag-deprecated', [ | ||
'The `--purge` flag has been deprecated.', | ||
'Please use `--content` instead.', | ||
]) | ||
if (!args['--content']) { | ||
args['--content'] = args['--purge'] | ||
} | ||
} | ||
if (args['--content']) { | ||
let files = args['--content'].split(/(?<!{[^}]+),/) | ||
let resolvedConfig = resolveConfigInternal(config, { content: { files } }) | ||
resolvedConfig.content.files = files | ||
resolvedConfig = validateConfig(resolvedConfig) | ||
return resolvedConfig | ||
} | ||
let resolvedConfig = resolveConfigInternal(config) | ||
resolvedConfig = validateConfig(resolvedConfig) | ||
return resolvedConfig | ||
} | ||
function extractFileGlobs(config) { | ||
return config.content.files | ||
.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' | ||
}) | ||
.map((glob) => normalizePath(glob)) | ||
} | ||
function extractRawContent(config) { | ||
return config.content.files.filter((file) => { | ||
return typeof file === 'object' && file !== null | ||
}) | ||
} | ||
function getChangedContent(config) { | ||
let changedContent = [] | ||
// Resolve globs from the content 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).slice(1), | ||
}) | ||
} | ||
// Resolve raw content in the tailwind config | ||
for (let { raw: content, extension = 'html' } of extractRawContent(config)) { | ||
changedContent.push({ content, extension }) | ||
} | ||
return changedContent | ||
} | ||
async function buildOnce() { | ||
let config = resolveConfig() | ||
let changedContent = getChangedContent(config) | ||
let tailwindPlugin = () => { | ||
return { | ||
postcssPlugin: 'tailwindcss', | ||
Once(root, { result }) { | ||
tailwind(({ createContext }) => { | ||
return () => { | ||
return createContext(config, changedContent) | ||
} | ||
})(root, result) | ||
}, | ||
} | ||
} | ||
tailwindPlugin.postcss = true | ||
let [beforePlugins, afterPlugins, postcssOptions] = includePostCss | ||
? await loadPostCssPlugins() | ||
: [[], [], {}] | ||
let plugins = [ | ||
...beforePlugins, | ||
tailwindPlugin, | ||
!args['--minify'] && formatNodes, | ||
...afterPlugins, | ||
!args['--no-autoprefixer'] && | ||
(() => { | ||
// Try to load a local `autoprefixer` version first | ||
try { | ||
return require('autoprefixer') | ||
} catch {} | ||
return lazyAutoprefixer() | ||
})(), | ||
args['--minify'] && | ||
(() => { | ||
let options = { preset: ['default', { cssDeclarationSorter: false }] } | ||
// Try to load a local `cssnano` version first | ||
try { | ||
return require('cssnano') | ||
} catch {} | ||
return lazyCssnano()(options) | ||
})(), | ||
].filter(Boolean) | ||
let postcss = loadPostcss() | ||
let processor = postcss(plugins) | ||
function processCSS(css) { | ||
let start = process.hrtime.bigint() | ||
return Promise.resolve() | ||
.then(() => (output ? fs.promises.mkdir(path.dirname(output), { recursive: true }) : null)) | ||
.then(() => processor.process(css, { ...postcssOptions, from: input, to: output })) | ||
.then((result) => { | ||
if (!output) { | ||
return process.stdout.write(result.css) | ||
} | ||
return Promise.all( | ||
[ | ||
outputFile(output, result.css), | ||
result.map && outputFile(output + '.map', result.map.toString()), | ||
].filter(Boolean) | ||
) | ||
}) | ||
.then(() => { | ||
let end = process.hrtime.bigint() | ||
console.error() | ||
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.') | ||
}) | ||
} | ||
let css = await (() => { | ||
// Piping in data, let's drain the stdin | ||
if (input === '-') { | ||
return drainStdin() | ||
} | ||
// Input file has been provided | ||
if (input) { | ||
return fs.readFileSync(path.resolve(input), 'utf8') | ||
} | ||
// No input file provided, fallback to default atrules | ||
return '@tailwind base; @tailwind components; @tailwind utilities' | ||
})() | ||
return processCSS(css) | ||
} | ||
let context = null | ||
async 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() | ||
} | ||
let [beforePlugins, afterPlugins] = includePostCss ? await loadPostCssPlugins() : [[], []] | ||
let plugins = [ | ||
...beforePlugins, | ||
'__TAILWIND_PLUGIN_POSITION__', | ||
!args['--minify'] && formatNodes, | ||
...afterPlugins, | ||
!args['--no-autoprefixer'] && | ||
(() => { | ||
// Try to load a local `autoprefixer` version first | ||
try { | ||
return require('autoprefixer') | ||
} catch {} | ||
return lazyAutoprefixer() | ||
})(), | ||
args['--minify'] && | ||
(() => { | ||
let options = { preset: ['default', { cssDeclarationSorter: false }] } | ||
// Try to load a local `cssnano` version first | ||
try { | ||
return require('cssnano') | ||
} catch {} | ||
return lazyCssnano()(options) | ||
})(), | ||
].filter(Boolean) | ||
async function rebuild(config) { | ||
env.DEBUG && console.time('Finished in') | ||
let tailwindPlugin = () => { | ||
return { | ||
postcssPlugin: 'tailwindcss', | ||
Once(root, { result }) { | ||
env.DEBUG && console.time('Compiling CSS') | ||
tailwind(({ createContext }) => { | ||
console.error() | ||
console.error('Rebuilding...') | ||
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') | ||
}, | ||
} | ||
} | ||
tailwindPlugin.postcss = true | ||
let tailwindPluginIdx = plugins.indexOf('__TAILWIND_PLUGIN_POSITION__') | ||
let copy = plugins.slice() | ||
copy.splice(tailwindPluginIdx, 1, tailwindPlugin) | ||
let postcss = loadPostcss() | ||
let processor = postcss(copy) | ||
function processCSS(css) { | ||
let start = process.hrtime.bigint() | ||
return Promise.resolve() | ||
.then(() => | ||
output ? fs.promises.mkdir(path.dirname(output), { recursive: true }) : null | ||
) | ||
.then(() => processor.process(css, { from: input, to: output })) | ||
.then(async (result) => { | ||
for (let message of result.messages) { | ||
if (message.type === 'dependency') { | ||
contextDependencies.add(message.file) | ||
} | ||
} | ||
watcher.add([...contextDependencies]) | ||
if (!output) { | ||
return process.stdout.write(result.css) | ||
} | ||
return Promise.all( | ||
[ | ||
outputFile(output, result.css), | ||
result.map && outputFile(output + '.map', result.map.toString()), | ||
].filter(Boolean) | ||
) | ||
}) | ||
.then(() => { | ||
let end = process.hrtime.bigint() | ||
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.') | ||
}) | ||
.catch((err) => { | ||
if (err.name === 'CssSyntaxError') { | ||
console.error(err.toString()) | ||
} else { | ||
console.error(err) | ||
} | ||
}) | ||
} | ||
let css = await (() => { | ||
// Piping in data, let's drain the stdin | ||
if (input === '-') { | ||
return drainStdin() | ||
} | ||
// Input file has been provided | ||
if (input) { | ||
return fs.readFileSync(path.resolve(input), 'utf8') | ||
} | ||
// No input file provided, fallback to default atrules | ||
return '@tailwind base; @tailwind components; @tailwind utilities' | ||
})() | ||
let result = await processCSS(css) | ||
env.DEBUG && console.timeEnd('Finished in') | ||
return result | ||
} | ||
let config = refreshConfig(configPath) | ||
if (input) { | ||
contextDependencies.add(path.resolve(input)) | ||
} | ||
watcher = chokidar.watch([...contextDependencies, ...extractFileGlobs(config)], { | ||
usePolling: shouldPoll, | ||
interval: shouldPoll ? pollInterval : undefined, | ||
ignoreInitial: true, | ||
awaitWriteFinish: shouldCoalesceWriteEvents | ||
? { | ||
stabilityThreshold: 50, | ||
pollInterval: pollInterval, | ||
} | ||
: false, | ||
}) | ||
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).slice(1), | ||
}) | ||
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).slice(1), | ||
}) | ||
await rebuild(config) | ||
}) | ||
}) | ||
chain = chain.then(() => { | ||
changedContent.push(...getChangedContent(config)) | ||
return rebuild(config) | ||
}) | ||
} | ||
if (shouldWatch) { | ||
/* Abort the watcher if stdin is closed to avoid zombie processes */ | ||
process.stdin.on('end', () => process.exit(0)) | ||
process.stdin.resume() | ||
startWatcher() | ||
} else { | ||
buildOnce() | ||
} | ||
} | ||
module.exports = require('./cli/index') |
@@ -1,1 +0,1 @@ | ||
export default ["preflight","container","accessibility","pointerEvents","visibility","position","inset","isolation","zIndex","order","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","float","clear","margin","boxSizing","display","aspectRatio","height","maxHeight","minHeight","width","minWidth","maxWidth","flex","flexShrink","flexGrow","flexBasis","tableLayout","borderCollapse","borderSpacing","transformOrigin","translate","rotate","skew","scale","transform","animation","cursor","touchAction","userSelect","resize","scrollSnapType","scrollSnapAlign","scrollSnapStop","scrollMargin","scrollPadding","listStylePosition","listStyleType","appearance","columns","breakBefore","breakInside","breakAfter","gridAutoColumns","gridAutoFlow","gridAutoRows","gridTemplateColumns","gridTemplateRows","flexDirection","flexWrap","placeContent","placeItems","alignContent","alignItems","justifyContent","justifyItems","gap","space","divideWidth","divideStyle","divideColor","divideOpacity","placeSelf","alignSelf","justifySelf","overflow","overscrollBehavior","scrollBehavior","textOverflow","whitespace","wordBreak","borderRadius","borderWidth","borderStyle","borderColor","borderOpacity","backgroundColor","backgroundOpacity","backgroundImage","gradientColorStops","boxDecorationBreak","backgroundSize","backgroundAttachment","backgroundClip","backgroundPosition","backgroundRepeat","backgroundOrigin","fill","stroke","strokeWidth","objectFit","objectPosition","padding","textAlign","textIndent","verticalAlign","fontFamily","fontSize","fontWeight","textTransform","fontStyle","fontVariantNumeric","lineHeight","letterSpacing","textColor","textOpacity","textDecoration","textDecorationColor","textDecorationStyle","textDecorationThickness","textUnderlineOffset","fontSmoothing","placeholderColor","placeholderOpacity","caretColor","accentColor","opacity","backgroundBlendMode","mixBlendMode","boxShadow","boxShadowColor","outlineStyle","outlineWidth","outlineOffset","outlineColor","ringWidth","ringColor","ringOpacity","ringOffsetWidth","ringOffsetColor","blur","brightness","contrast","dropShadow","grayscale","hueRotate","invert","saturate","sepia","filter","backdropBlur","backdropBrightness","backdropContrast","backdropGrayscale","backdropHueRotate","backdropInvert","backdropOpacity","backdropSaturate","backdropSepia","backdropFilter","transitionProperty","transitionDelay","transitionDuration","transitionTimingFunction","willChange","content"] | ||
export default ["preflight","container","accessibility","pointerEvents","visibility","position","inset","isolation","zIndex","order","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","float","clear","margin","boxSizing","lineClamp","display","aspectRatio","size","height","maxHeight","minHeight","width","minWidth","maxWidth","flex","flexShrink","flexGrow","flexBasis","tableLayout","captionSide","borderCollapse","borderSpacing","transformOrigin","translate","rotate","skew","scale","transform","animation","cursor","touchAction","userSelect","resize","scrollSnapType","scrollSnapAlign","scrollSnapStop","scrollMargin","scrollPadding","listStylePosition","listStyleType","listStyleImage","appearance","columns","breakBefore","breakInside","breakAfter","gridAutoColumns","gridAutoFlow","gridAutoRows","gridTemplateColumns","gridTemplateRows","flexDirection","flexWrap","placeContent","placeItems","alignContent","alignItems","justifyContent","justifyItems","gap","space","divideWidth","divideStyle","divideColor","divideOpacity","placeSelf","alignSelf","justifySelf","overflow","overscrollBehavior","scrollBehavior","textOverflow","hyphens","whitespace","textWrap","wordBreak","borderRadius","borderWidth","borderStyle","borderColor","borderOpacity","backgroundColor","backgroundOpacity","backgroundImage","gradientColorStops","boxDecorationBreak","backgroundSize","backgroundAttachment","backgroundClip","backgroundPosition","backgroundRepeat","backgroundOrigin","fill","stroke","strokeWidth","objectFit","objectPosition","padding","textAlign","textIndent","verticalAlign","fontFamily","fontSize","fontWeight","textTransform","fontStyle","fontVariantNumeric","lineHeight","letterSpacing","textColor","textOpacity","textDecoration","textDecorationColor","textDecorationStyle","textDecorationThickness","textUnderlineOffset","fontSmoothing","placeholderColor","placeholderOpacity","caretColor","accentColor","opacity","backgroundBlendMode","mixBlendMode","boxShadow","boxShadowColor","outlineStyle","outlineWidth","outlineOffset","outlineColor","ringWidth","ringColor","ringOpacity","ringOffsetWidth","ringOffsetColor","blur","brightness","contrast","dropShadow","grayscale","hueRotate","invert","saturate","sepia","filter","backdropBlur","backdropBrightness","backdropContrast","backdropGrayscale","backdropHueRotate","backdropInvert","backdropOpacity","backdropSaturate","backdropSepia","backdropFilter","transitionProperty","transitionDelay","transitionDuration","transitionTimingFunction","willChange","contain","content","forcedColorAdjust"] |
@@ -6,7 +6,15 @@ import colors from 'picocolors' | ||
optimizeUniversalDefaults: false, | ||
generalizedModifiers: true, | ||
disableColorOpacityUtilitiesByDefault: false, | ||
relativeContentPathsByDefault: false, | ||
} | ||
let featureFlags = { | ||
future: [], | ||
experimental: ['optimizeUniversalDefaults'], | ||
future: [ | ||
'hoverOnlyWhenSupported', | ||
'respectDefaultRingColorOpacity', | ||
'disableColorOpacityUtilitiesByDefault', | ||
'relativeContentPathsByDefault', | ||
], | ||
experimental: ['optimizeUniversalDefaults', 'generalizedModifiers'], | ||
} | ||
@@ -13,0 +21,0 @@ |
@@ -1,42 +0,1 @@ | ||
import setupTrackingContext from './lib/setupTrackingContext' | ||
import processTailwindFeatures from './processTailwindFeatures' | ||
import { env } from './lib/sharedState' | ||
module.exports = function tailwindcss(configOrPath) { | ||
return { | ||
postcssPlugin: 'tailwindcss', | ||
plugins: [ | ||
env.DEBUG && | ||
function (root) { | ||
console.log('\n') | ||
console.time('JIT TOTAL') | ||
return root | ||
}, | ||
function (root, result) { | ||
let context = setupTrackingContext(configOrPath) | ||
if (root.type === 'document') { | ||
let roots = root.nodes.filter((node) => node.type === 'root') | ||
for (const root of roots) { | ||
if (root.type === 'root') { | ||
processTailwindFeatures(context)(root, result) | ||
} | ||
} | ||
return | ||
} | ||
processTailwindFeatures(context)(root, result) | ||
}, | ||
env.DEBUG && | ||
function (root) { | ||
console.timeEnd('JIT TOTAL') | ||
console.log('\n') | ||
return root | ||
}, | ||
].filter(Boolean), | ||
} | ||
} | ||
module.exports.postcss = true | ||
module.exports = require('./plugin') |
import * as regex from './regex' | ||
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly' | ||
@@ -14,6 +15,32 @@ export function defaultExtractor(context) { | ||
for (let pattern of patterns) { | ||
results.push(...(content.match(pattern) ?? [])) | ||
for (let result of content.match(pattern) ?? []) { | ||
results.push(clipAtBalancedParens(result)) | ||
} | ||
} | ||
return results.filter((v) => v !== undefined).map(clipAtBalancedParens) | ||
// Extract any subclasses from languages like Slim and Pug, eg: | ||
// div.flex.px-5.underline | ||
for (let result of results.slice()) { | ||
let segments = splitAtTopLevelOnly(result, '.') | ||
for (let idx = 0; idx < segments.length; idx++) { | ||
let segment = segments[idx] | ||
if (idx >= segments.length - 1) { | ||
results.push(segment) | ||
continue | ||
} | ||
// If the next segment is a number, discard both, for example seeing | ||
// `px-1` and `5` means the real candidate was `px-1.5` which is already | ||
// captured. | ||
let next = Number(segments[idx + 1]) | ||
if (isNaN(next)) { | ||
results.push(segment) | ||
} else { | ||
idx++ | ||
} | ||
} | ||
} | ||
return results | ||
} | ||
@@ -24,62 +51,110 @@ } | ||
let separator = context.tailwindConfig.separator | ||
let prefix = | ||
context.tailwindConfig.prefix !== '' | ||
? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)])) | ||
: '' | ||
yield regex.pattern([ | ||
// Variants | ||
'((?=((', | ||
regex.any( | ||
[ | ||
regex.pattern([/([^\s"'\[\\]+-)?\[[^\s"'\\]+\]/, separator]), | ||
regex.pattern([/[^\s"'\[\\]+/, separator]), | ||
], | ||
true | ||
), | ||
')+))\\2)?', | ||
let utility = regex.any([ | ||
// Arbitrary properties (without square brackets) | ||
/\[[^\s:'"`]+:[^\s\[\]]+\]/, | ||
// Important (optional) | ||
/!?/, | ||
// Arbitrary properties with balanced square brackets | ||
// This is a targeted fix to continue to allow theme() | ||
// with square brackets to work in arbitrary properties | ||
// while fixing a problem with the regex matching too much | ||
/\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/, | ||
regex.any([ | ||
// Arbitrary properties | ||
/\[[^\s:'"]+:[^\s\]]+\]/, | ||
// Utilities | ||
regex.pattern([ | ||
// Utility Name / Group Name | ||
// Utilities | ||
regex.pattern([ | ||
// Utility Name / Group Name | ||
regex.any([ | ||
/-?(?:\w+)/, | ||
// Normal/Arbitrary values | ||
regex.optional( | ||
regex.any([ | ||
regex.pattern([ | ||
// Arbitrary values | ||
/-\[[^\s:]+\]/, | ||
// This is here to make sure @container supports everything that other utilities do | ||
/@(?:\w+)/, | ||
]), | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"\\$]*)?/, | ||
// Normal/Arbitrary values | ||
regex.optional( | ||
regex.any([ | ||
regex.pattern([ | ||
// Arbitrary values | ||
regex.any([ | ||
/-(?:\w+-)*\['[^\s]+'\]/, | ||
/-(?:\w+-)*\["[^\s]+"\]/, | ||
/-(?:\w+-)*\[`[^\s]+`\]/, | ||
/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/, | ||
]), | ||
regex.pattern([ | ||
// Arbitrary values | ||
/-\[[^\s]+\]/, | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"`\\><$]*)?/, | ||
]), | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"\\$]*)?/, | ||
regex.pattern([ | ||
// Arbitrary values | ||
regex.any([ | ||
/-(?:\w+-)*\['[^\s]+'\]/, | ||
/-(?:\w+-)*\["[^\s]+"\]/, | ||
/-(?:\w+-)*\[`[^\s]+`\]/, | ||
/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/, | ||
]), | ||
// Normal values w/o quotes — may include an opacity modifier | ||
/[-\/][^\s'"\\$={]*/, | ||
]) | ||
), | ||
]), | ||
// Not immediately followed by an `{[(` | ||
/(?![{([]])/, | ||
// optionally followed by an opacity modifier | ||
/(?:\/[^\s'"`\\$]*)?/, | ||
]), | ||
// Normal values w/o quotes — may include an opacity modifier | ||
/[-\/][^\s'"`\\$={><]*/, | ||
]) | ||
), | ||
]), | ||
]) | ||
let variantPatterns = [ | ||
// Without quotes | ||
regex.any([ | ||
// This is here to provide special support for the `@` variant | ||
regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]), | ||
// With variant modifier (e.g.: group-[..]/modifier) | ||
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/[\w_-]+/, separator]), | ||
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]), | ||
regex.pattern([/[^\s"'`\[\\]+/, separator]), | ||
]), | ||
// With quotes allowed | ||
regex.any([ | ||
// With variant modifier (e.g.: group-[..]/modifier) | ||
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/[\w_-]+/, separator]), | ||
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]), | ||
regex.pattern([/[^\s`\[\\]+/, separator]), | ||
]), | ||
] | ||
for (const variantPattern of variantPatterns) { | ||
yield regex.pattern([ | ||
// Variants | ||
'((?=((', | ||
variantPattern, | ||
')+))\\2)?', | ||
// Important (optional) | ||
/!?/, | ||
prefix, | ||
utility, | ||
]) | ||
} | ||
// 5. Inner matches | ||
// yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g | ||
yield /[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g | ||
} | ||
@@ -158,3 +233,3 @@ | ||
if (depth < 0) { | ||
return input.substring(0, match.index) | ||
return input.substring(0, match.index - 1) | ||
} | ||
@@ -161,0 +236,0 @@ |
import dlv from 'dlv' | ||
import didYouMean from 'didyoumean' | ||
import transformThemeValue from '../util/transformThemeValue' | ||
import parseValue from 'postcss-value-parser' | ||
import parseValue from '../value-parser/index' | ||
import { normalizeScreens } from '../util/normalizeScreens' | ||
import buildMediaQuery from '../util/buildMediaQuery' | ||
import { toPath } from '../util/toPath' | ||
import { withAlphaValue } from '../util/withAlphaVariable' | ||
import { parseColorFormat } from '../util/pluginUtils' | ||
import log from '../util/log' | ||
@@ -40,6 +43,4 @@ function isObject(input) { | ||
function validatePath(config, path, defaultValue) { | ||
const pathString = Array.isArray(path) | ||
? pathToString(path) | ||
: path.replace(/^['"]+/g, '').replace(/['"]+$/g, '') | ||
function validatePath(config, path, defaultValue, themeOpts = {}) { | ||
const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+|['"]+$/g, '') | ||
const pathSegments = Array.isArray(path) ? path : toPath(pathString) | ||
@@ -118,3 +119,3 @@ const value = dlv(config.theme, pathSegments, defaultValue) | ||
isValid: true, | ||
value: transformThemeValue(themeSection)(value), | ||
value: transformThemeValue(themeSection)(value, themeOpts), | ||
} | ||
@@ -150,2 +151,5 @@ } | ||
function resolveFunctions(node, input, functions) { | ||
let hasAnyFn = Object.keys(functions).some((fn) => input.includes(`${fn}(`)) | ||
if (!hasAnyFn) return input | ||
return parseValue(input) | ||
@@ -163,6 +167,47 @@ .walk((vNode) => { | ||
export default function ({ tailwindConfig: config }) { | ||
/** | ||
* @param {string} path | ||
* @returns {Iterable<[path: string, alpha: string|undefined]>} | ||
*/ | ||
function* toPaths(path) { | ||
// Strip quotes from beginning and end of string | ||
// This allows the alpha value to be present inside of quotes | ||
path = path.replace(/^['"]+|['"]+$/g, '') | ||
let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/) | ||
let alpha = undefined | ||
yield [path, undefined] | ||
if (matches) { | ||
path = matches[1] | ||
alpha = matches[2] | ||
yield [path, alpha] | ||
} | ||
} | ||
/** | ||
* | ||
* @param {any} config | ||
* @param {string} path | ||
* @param {any} defaultValue | ||
*/ | ||
function resolvePath(config, path, defaultValue) { | ||
const results = Array.from(toPaths(path)).map(([path, alpha]) => { | ||
return Object.assign(validatePath(config, path, defaultValue, { opacityValue: alpha }), { | ||
resolvedPath: path, | ||
alpha, | ||
}) | ||
}) | ||
return results.find((result) => result.isValid) ?? results[0] | ||
} | ||
export default function (context) { | ||
let config = context.tailwindConfig | ||
let functions = { | ||
theme: (node, path, ...defaultValue) => { | ||
const { isValid, value, error } = validatePath( | ||
let { isValid, value, error, alpha } = resolvePath( | ||
config, | ||
@@ -174,5 +219,34 @@ path, | ||
if (!isValid) { | ||
let parentNode = node.parent | ||
let candidate = parentNode?.raws.tailwind?.candidate | ||
if (parentNode && candidate !== undefined) { | ||
// Remove this utility from any caches | ||
context.markInvalidUtilityNode(parentNode) | ||
// Remove the CSS node from the markup | ||
parentNode.remove() | ||
// Show a warning | ||
log.warn('invalid-theme-key-in-class', [ | ||
`The utility \`${candidate}\` contains an invalid theme value and was not generated.`, | ||
]) | ||
return | ||
} | ||
throw node.error(error) | ||
} | ||
let maybeColor = parseColorFormat(value) | ||
let isColorFunction = maybeColor !== undefined && typeof maybeColor === 'function' | ||
if (alpha !== undefined || isColorFunction) { | ||
if (alpha === undefined) { | ||
alpha = 1.0 | ||
} | ||
value = withAlphaValue(maybeColor, alpha, maybeColor) | ||
} | ||
return value | ||
@@ -179,0 +253,0 @@ }, |
@@ -5,4 +5,5 @@ import postcss from 'postcss' | ||
import { resolveMatches } from './generateRules' | ||
import bigSign from '../util/bigSign' | ||
import escapeClassName from '../util/escapeClassName' | ||
import { applyImportantSelector } from '../util/applyImportantSelector' | ||
import { movePseudos } from '../util/pseudoElements' | ||
@@ -38,3 +39,3 @@ /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */ | ||
let selectorExtractor = parser((root) => root.nodes.map((node) => node.toString())) | ||
let selectorExtractor = parser() | ||
@@ -45,3 +46,3 @@ /** | ||
function extractSelectors(ruleSelectors) { | ||
return selectorExtractor.transformSync(ruleSelectors) | ||
return selectorExtractor.astSync(ruleSelectors) | ||
} | ||
@@ -155,5 +156,3 @@ | ||
let highestOffset = context.layerOrder.user >> 4n | ||
root.walkRules((rule, idx) => { | ||
root.walkRules((rule) => { | ||
// Ignore rules generated by Tailwind | ||
@@ -168,2 +167,3 @@ for (let node of pathToRoot(rule)) { | ||
let container = nestedClone(rule) | ||
let sort = context.offsets.create('user') | ||
@@ -177,3 +177,3 @@ for (let className of extractClasses(rule)) { | ||
layer: 'user', | ||
sort: BigInt(idx) + highestOffset, | ||
sort, | ||
important: false, | ||
@@ -308,26 +308,93 @@ }, | ||
* Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover` | ||
* | ||
* @param {string} selector | ||
* @param {string} utilitySelectors | ||
* @param {string} candidate | ||
*/ | ||
// TODO: Should we use postcss-selector-parser for this instead? | ||
function replaceSelector(selector, utilitySelectors, candidate) { | ||
let needle = `.${escapeClassName(candidate)}` | ||
let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])] | ||
let selectorList = extractSelectors(selector) | ||
let utilitySelectorsList = extractSelectors(utilitySelectors) | ||
let candidateList = extractSelectors(`.${escapeClassName(candidate)}`) | ||
let candidateClass = candidateList.nodes[0].nodes[0] | ||
return extractSelectors(selector) | ||
.map((s) => { | ||
let replaced = [] | ||
selectorList.each((sel) => { | ||
/** @type {Set<import('postcss-selector-parser').Selector>} */ | ||
let replaced = new Set() | ||
for (let utilitySelector of utilitySelectorsList) { | ||
let replacedSelector = utilitySelector | ||
for (const needle of needles) { | ||
replacedSelector = replacedSelector.replace(needle, s) | ||
utilitySelectorsList.each((utilitySelector) => { | ||
let hasReplaced = false | ||
utilitySelector = utilitySelector.clone() | ||
utilitySelector.walkClasses((node) => { | ||
if (node.value !== candidateClass.value) { | ||
return | ||
} | ||
if (replacedSelector === utilitySelector) { | ||
continue | ||
// Don't replace multiple instances of the same class | ||
// This is theoretically correct but only partially | ||
// We'd need to generate every possible permutation of the replacement | ||
// For example with `.foo + .foo { … }` and `section { @apply foo; }` | ||
// We'd need to generate all of these: | ||
// - `.foo + .foo` | ||
// - `.foo + section` | ||
// - `section + .foo` | ||
// - `section + section` | ||
if (hasReplaced) { | ||
return | ||
} | ||
replaced.push(replacedSelector) | ||
// Since you can only `@apply` class names this is sufficient | ||
// We want to replace the matched class name with the selector the user is using | ||
// Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)` | ||
node.replaceWith(...sel.nodes.map((node) => node.clone())) | ||
// Record that we did something and we want to use this new selector | ||
replaced.add(utilitySelector) | ||
hasReplaced = true | ||
}) | ||
}) | ||
// Sort tag names before class names (but only sort each group (separated by a combinator) | ||
// separately and not in total) | ||
// This happens when replacing `.bar` in `.foo.bar` with a tag like `section` | ||
for (let sel of replaced) { | ||
let groups = [[]] | ||
for (let node of sel.nodes) { | ||
if (node.type === 'combinator') { | ||
groups.push(node) | ||
groups.push([]) | ||
} else { | ||
let last = groups[groups.length - 1] | ||
last.push(node) | ||
} | ||
} | ||
return replaced.join(', ') | ||
}) | ||
.join(', ') | ||
sel.nodes = [] | ||
for (let group of groups) { | ||
if (Array.isArray(group)) { | ||
group.sort((a, b) => { | ||
if (a.type === 'tag' && b.type === 'class') { | ||
return -1 | ||
} else if (a.type === 'class' && b.type === 'tag') { | ||
return 1 | ||
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) { | ||
return -1 | ||
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') { | ||
return 1 | ||
} | ||
return 0 | ||
}) | ||
} | ||
sel.nodes = sel.nodes.concat(group) | ||
} | ||
} | ||
sel.replaceWith(...replaced) | ||
}) | ||
return selectorList.toString() | ||
} | ||
@@ -347,3 +414,3 @@ | ||
if (apply.parent.name === 'screen') { | ||
const screenType = apply.parent.params | ||
let screenType = apply.parent.params | ||
@@ -376,2 +443,19 @@ throw apply.error( | ||
// Verify that we can apply the class | ||
for (let [, rule] of rules) { | ||
if (rule.type === 'atrule') { | ||
continue | ||
} | ||
rule.walkRules(() => { | ||
throw apply.error( | ||
[ | ||
`The \`${applyCandidate}\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.`, | ||
'Rewrite the selector without nesting or configure the `tailwindcss/nesting` plugin:', | ||
'https://tailwindcss.com/docs/using-with-preprocessors#nesting', | ||
].join('\n') | ||
) | ||
}) | ||
} | ||
candidates.push([applyCandidate, important, rules]) | ||
@@ -381,3 +465,3 @@ } | ||
for (const [parent, [candidates, atApplySource]] of perParentApplies) { | ||
for (let [parent, [candidates, atApplySource]] of perParentApplies) { | ||
let siblings = [] | ||
@@ -499,2 +583,9 @@ | ||
// If the selector becomes empty after replacing the important selector | ||
// This means that it's the same as the parent selector and we don't want to replace it | ||
// Otherwise we'll crash | ||
if (parentSelector === '') { | ||
parentSelector = parent.selector | ||
} | ||
rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate) | ||
@@ -504,3 +595,3 @@ | ||
if (importantSelector && parentSelector !== parent.selector) { | ||
rule.selector = `${importantSelector} ${rule.selector}` | ||
rule.selector = applyImportantSelector(rule.selector, importantSelector) | ||
} | ||
@@ -511,11 +602,18 @@ | ||
}) | ||
// Move pseudo elements to the end of the selector (if necessary) | ||
let selector = parser().astSync(rule.selector) | ||
selector.each((sel) => movePseudos(sel)) | ||
rule.selector = selector.toString() | ||
}) | ||
} | ||
// It could be that the node we were inserted was removed because the class didn't match | ||
// If that was the *only* rule in the parent, then we have nothing add so we skip it | ||
if (!root.nodes[0]) { | ||
continue | ||
} | ||
// Insert it | ||
siblings.push([ | ||
// Ensure that when we are sorting, that we take the layer order into account | ||
{ ...meta, sort: meta.sort | context.layerOrder[meta.layer] }, | ||
root.nodes[0], | ||
]) | ||
siblings.push([meta.sort, root.nodes[0]]) | ||
} | ||
@@ -525,3 +623,3 @@ } | ||
// Inject the rules, sorted, correctly | ||
let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1]) | ||
let nodes = context.offsets.sort(siblings).map((s) => s[1]) | ||
@@ -528,0 +626,0 @@ // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 } |
@@ -1,5 +0,5 @@ | ||
import LRU from 'quick-lru' | ||
import fs from 'fs' | ||
import LRU from '@alloc/quick-lru' | ||
import * as sharedState from './sharedState' | ||
import { generateRules } from './generateRules' | ||
import bigSign from '../util/bigSign' | ||
import log from '../util/log' | ||
@@ -77,4 +77,9 @@ import cloneNodes from '../util/cloneNodes' | ||
/** | ||
* | ||
* @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules | ||
* @param {*} context | ||
*/ | ||
function buildStylesheet(rules, context) { | ||
let sortedRules = rules.sort(([a], [z]) => bigSign(a - z)) | ||
let sortedRules = context.offsets.sort(rules) | ||
@@ -87,44 +92,6 @@ let returnValue = { | ||
variants: new Set(), | ||
// All the CSS that is not Tailwind related can be put in this bucket. This | ||
// will make it easier to later use this information when we want to | ||
// `@apply` for example. The main reason we do this here is because we | ||
// still need to make sure the order is correct. Last but not least, we | ||
// will make sure to always re-inject this section into the css, even if | ||
// certain rules were not used. This means that it will look like a no-op | ||
// from the user's perspective, but we gathered all the useful information | ||
// we need. | ||
user: new Set(), | ||
} | ||
for (let [sort, rule] of sortedRules) { | ||
if (sort >= context.minimumScreen) { | ||
returnValue.variants.add(rule) | ||
continue | ||
} | ||
if (sort & context.layerOrder.base) { | ||
returnValue.base.add(rule) | ||
continue | ||
} | ||
if (sort & context.layerOrder.defaults) { | ||
returnValue.defaults.add(rule) | ||
continue | ||
} | ||
if (sort & context.layerOrder.components) { | ||
returnValue.components.add(rule) | ||
continue | ||
} | ||
if (sort & context.layerOrder.utilities) { | ||
returnValue.utilities.add(rule) | ||
continue | ||
} | ||
if (sort & context.layerOrder.user) { | ||
returnValue.user.add(rule) | ||
continue | ||
} | ||
returnValue[sort.layer].add(rule) | ||
} | ||
@@ -136,3 +103,3 @@ | ||
export default function expandTailwindAtRules(context) { | ||
return (root) => { | ||
return async (root) => { | ||
let layerNodes = { | ||
@@ -164,3 +131,3 @@ base: null, | ||
// Find potential rules in changed files | ||
let candidates = new Set([sharedState.NOT_ON_DEMAND]) | ||
let candidates = new Set([...(context.candidates ?? []), sharedState.NOT_ON_DEMAND]) | ||
let seen = new Set() | ||
@@ -170,8 +137,23 @@ | ||
for (let { content, extension } of context.changedContent) { | ||
let transformer = getTransformer(context.tailwindConfig, extension) | ||
let extractor = getExtractor(context, extension) | ||
getClassCandidates(transformer(content), extractor, candidates, seen) | ||
/** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */ | ||
let regexParserContent = [] | ||
for (let item of context.changedContent) { | ||
let transformer = getTransformer(context.tailwindConfig, item.extension) | ||
let extractor = getExtractor(context, item.extension) | ||
regexParserContent.push([item, { transformer, extractor }]) | ||
} | ||
const BATCH_SIZE = 500 | ||
for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) { | ||
let batch = regexParserContent.slice(i, i + BATCH_SIZE) | ||
await Promise.all( | ||
batch.map(async ([{ file, content }, { transformer, extractor }]) => { | ||
content = file ? await fs.promises.readFile(file, 'utf8') : content | ||
getClassCandidates(transformer(content), extractor, candidates, seen) | ||
}) | ||
) | ||
} | ||
env.DEBUG && console.timeEnd('Reading changed files') | ||
@@ -185,3 +167,12 @@ | ||
env.DEBUG && console.time('Generate rules') | ||
let rules = generateRules(candidates, context) | ||
env.DEBUG && console.time('Sorting candidates') | ||
let sortedCandidates = new Set( | ||
[...candidates].sort((a, z) => { | ||
if (a === z) return 0 | ||
if (a < z) return -1 | ||
return 1 | ||
}) | ||
) | ||
env.DEBUG && console.timeEnd('Sorting candidates') | ||
generateRules(sortedCandidates, context) | ||
env.DEBUG && console.timeEnd('Generate rules') | ||
@@ -192,6 +183,2 @@ | ||
if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) { | ||
for (let rule of rules) { | ||
context.ruleCache.add(rule) | ||
} | ||
context.stylesheetCache = buildStylesheet([...context.ruleCache], context) | ||
@@ -270,2 +257,5 @@ } | ||
// TODO: Why is the root node having no source location for `end` possible? | ||
root.source.end = root.source.end ?? root.source.start | ||
// If we've got a utility layer and no utilities are generated there's likely something wrong | ||
@@ -272,0 +262,0 @@ const hasUtilityVariants = variantNodes.some( |
@@ -6,11 +6,17 @@ import postcss from 'postcss' | ||
import prefixSelector from '../util/prefixSelector' | ||
import { updateAllClasses } from '../util/pluginUtils' | ||
import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils' | ||
import log from '../util/log' | ||
import * as sharedState from './sharedState' | ||
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector' | ||
import { | ||
formatVariantSelector, | ||
finalizeSelector, | ||
eliminateIrrelevantSelectors, | ||
} from '../util/formatVariantSelector' | ||
import { asClass } from '../util/nameClass' | ||
import { normalize } from '../util/dataTypes' | ||
import { isValidVariantFormatString, parseVariant } from './setupContextUtils' | ||
import isValidArbitraryValue from '../util/isValidArbitraryValue' | ||
import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils' | ||
import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue' | ||
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js' | ||
import { flagEnabled } from '../featureFlags' | ||
import { applyImportantSelector } from '../util/applyImportantSelector' | ||
@@ -21,3 +27,3 @@ let classNameParser = selectorParser((selectors) => { | ||
function getClassNameFromSelector(selector) { | ||
export function getClassNameFromSelector(selector) { | ||
return classNameParser.transformSync(selector) | ||
@@ -38,2 +44,3 @@ } | ||
let dashIdx | ||
let wasSlash = false | ||
@@ -45,3 +52,13 @@ if (lastIndex === Infinity && candidate.endsWith(']')) { | ||
// eg. string[] | ||
dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1 | ||
if (candidate[bracketIdx - 1] === '-') { | ||
dashIdx = bracketIdx - 1 | ||
} else if (candidate[bracketIdx - 1] === '/') { | ||
dashIdx = bracketIdx - 1 | ||
wasSlash = true | ||
} else { | ||
dashIdx = -1 | ||
} | ||
} else if (lastIndex === Infinity && candidate.includes('/')) { | ||
dashIdx = candidate.lastIndexOf('/') | ||
wasSlash = true | ||
} else { | ||
@@ -56,7 +73,12 @@ dashIdx = candidate.lastIndexOf('-', lastIndex) | ||
let prefix = candidate.slice(0, dashIdx) | ||
let modifier = candidate.slice(dashIdx + 1) | ||
let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1) | ||
lastIndex = dashIdx - 1 | ||
// TODO: This feels a bit hacky | ||
if (prefix === '' || modifier === '/') { | ||
continue | ||
} | ||
yield [prefix, modifier] | ||
lastIndex = dashIdx - 1 | ||
} | ||
@@ -102,15 +124,34 @@ } | ||
} | ||
let result = [] | ||
function isInKeyframes(rule) { | ||
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes' | ||
} | ||
for (let [meta, rule] of matches) { | ||
let container = postcss.root({ nodes: [rule.clone()] }) | ||
container.walkRules((r) => { | ||
r.selector = updateAllClasses(r.selector, (className) => { | ||
if (className === classCandidate) { | ||
return `!${className}` | ||
} | ||
return className | ||
}) | ||
// Declarations inside keyframes cannot be marked as important | ||
// They will be ignored by the browser | ||
if (isInKeyframes(r)) { | ||
return | ||
} | ||
let ast = selectorParser().astSync(r.selector) | ||
// Remove extraneous selectors that do not include the base candidate | ||
ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate)) | ||
// Update all instances of the base candidate to include the important marker | ||
updateAllClasses(ast, (className) => | ||
className === classCandidate ? `!${className}` : className | ||
) | ||
r.selector = ast.toString() | ||
r.walkDecls((d) => (d.important = true)) | ||
}) | ||
result.push([{ ...meta, important: true }, container.nodes[0]]) | ||
@@ -136,8 +177,46 @@ } | ||
let args | ||
/** @type {{modifier: string | null, value: string | null}} */ | ||
let args = { modifier: null, value: sharedState.NONE } | ||
// Find partial arbitrary variants | ||
// Retrieve "modifier" | ||
{ | ||
let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/') | ||
// This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500` | ||
// In this case 1/10 is a value but /20 is a modifier | ||
if (modifiers.length > 1) { | ||
baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/') | ||
modifiers = modifiers.slice(-1) | ||
} | ||
if (modifiers.length && !context.variantMap.has(variant)) { | ||
variant = baseVariant | ||
args.modifier = modifiers[0] | ||
if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) { | ||
return [] | ||
} | ||
} | ||
} | ||
// Retrieve "arbitrary value" | ||
if (variant.endsWith(']') && !variant.startsWith('[')) { | ||
args = variant.slice(variant.lastIndexOf('[') + 1, -1) | ||
variant = variant.slice(0, variant.indexOf(args) - 1 /* - */ - 1 /* [ */) | ||
// We either have: | ||
// @[200px] | ||
// group-[:hover] | ||
// | ||
// But we don't want: | ||
// @-[200px] (`-` is incorrect) | ||
// group[:hover] (`-` is missing) | ||
let match = /(.)(-?)\[(.*)\]/g.exec(variant) | ||
if (match) { | ||
let [, char, separator, value] = match | ||
// @-[200px] case | ||
if (char === '@' && separator === '-') return [] | ||
// group[:hover] case | ||
if (char !== '@' && separator === '') return [] | ||
variant = variant.replace(`${separator}[${value}]`, '') | ||
args.value = value | ||
} | ||
} | ||
@@ -147,19 +226,36 @@ | ||
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) { | ||
let sort = context.offsets.recordVariant(variant) | ||
let selector = normalize(variant.slice(1, -1)) | ||
let selectors = splitAtTopLevelOnly(selector, ',') | ||
if (!isValidVariantFormatString(selector)) { | ||
// We do not support multiple selectors for arbitrary variants | ||
if (selectors.length > 1) { | ||
return [] | ||
} | ||
let fn = parseVariant(selector) | ||
if (!selectors.every(isValidVariantFormatString)) { | ||
return [] | ||
} | ||
let sort = Array.from(context.variantOrder.values()).pop() << 1n | ||
context.variantMap.set(variant, [[sort, fn]]) | ||
context.variantOrder.set(variant, sort) | ||
let records = selectors.map((sel, idx) => [ | ||
context.offsets.applyParallelOffset(sort, idx), | ||
parseVariant(sel.trim()), | ||
]) | ||
context.variantMap.set(variant, records) | ||
} | ||
if (context.variantMap.has(variant)) { | ||
let variantFunctionTuples = context.variantMap.get(variant) | ||
let isArbitraryVariant = isArbitraryValue(variant) | ||
let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {} | ||
let variantFunctionTuples = context.variantMap.get(variant).slice() | ||
let result = [] | ||
let respectPrefix = (() => { | ||
if (isArbitraryVariant) return false | ||
if (internalFeatures.respectPrefix === false) return false | ||
return true | ||
})() | ||
for (let [meta, rule] of matches) { | ||
@@ -173,11 +269,13 @@ // Don't generate variants for user css | ||
for (let [variantSort, variantFunction] of variantFunctionTuples) { | ||
let clone = container.clone() | ||
for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) { | ||
let clone = (containerFromArray ?? container).clone() | ||
let collectedFormats = [] | ||
let originals = new Map() | ||
function prepareBackup() { | ||
if (originals.size > 0) return // Already prepared, chicken out | ||
clone.walkRules((rule) => originals.set(rule, rule.selector)) | ||
// Already prepared, chicken out | ||
if (clone.raws.neededBackup) { | ||
return | ||
} | ||
clone.raws.neededBackup = true | ||
clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector)) | ||
} | ||
@@ -222,3 +320,6 @@ | ||
format(selectorFormat) { | ||
collectedFormats.push(selectorFormat) | ||
collectedFormats.push({ | ||
format: selectorFormat, | ||
respectPrefix, | ||
}) | ||
}, | ||
@@ -228,4 +329,27 @@ args, | ||
// It can happen that a list of format strings is returned from within the function. In that | ||
// case, we have to process them as well. We can use the existing `variantSort`. | ||
if (Array.isArray(ruleWithVariant)) { | ||
for (let [idx, variantFunction] of ruleWithVariant.entries()) { | ||
// This is a little bit scary since we are pushing to an array of items that we are | ||
// currently looping over. However, you can also think of it like a processing queue | ||
// where you keep handling jobs until everything is done and each job can queue more | ||
// jobs if needed. | ||
variantFunctionTuples.push([ | ||
context.offsets.applyParallelOffset(variantSort, idx), | ||
variantFunction, | ||
// If the clone has been modified we have to pass that back | ||
// though so each rule can use the modified container | ||
clone.clone(), | ||
]) | ||
} | ||
continue | ||
} | ||
if (typeof ruleWithVariant === 'string') { | ||
collectedFormats.push(ruleWithVariant) | ||
collectedFormats.push({ | ||
format: ruleWithVariant, | ||
respectPrefix, | ||
}) | ||
} | ||
@@ -237,9 +361,11 @@ | ||
// We filled the `originals`, therefore we assume that somebody touched | ||
// We had to backup selectors, therefore we assume that somebody touched | ||
// `container` or `modifySelectors`. Let's see if they did, so that we | ||
// can restore the selectors, and collect the format strings. | ||
if (originals.size > 0) { | ||
if (clone.raws.neededBackup) { | ||
delete clone.raws.neededBackup | ||
clone.walkRules((rule) => { | ||
if (!originals.has(rule)) return | ||
let before = originals.get(rule) | ||
let before = rule.raws.originalSelector | ||
if (!before) return | ||
delete rule.raws.originalSelector | ||
if (before === rule.selector) return // No mutation happened | ||
@@ -270,3 +396,6 @@ | ||
// format: .foo & | ||
collectedFormats.push(modified.replace(rebuiltBase, '&')) | ||
collectedFormats.push({ | ||
format: modified.replace(rebuiltBase, '&'), | ||
respectPrefix, | ||
}) | ||
rule.selector = before | ||
@@ -285,3 +414,7 @@ }) | ||
...meta, | ||
sort: variantSort | meta.sort, | ||
sort: context.offsets.applyVariantOffset( | ||
meta.sort, | ||
variantSort, | ||
Object.assign(args, context.variantOptions.get(variant)) | ||
), | ||
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats), | ||
@@ -350,3 +483,3 @@ }, | ||
node.walkDecls((decl) => { | ||
if (!isParsableCssValue(decl.name, decl.value)) { | ||
if (!isParsableCssValue(decl.prop, decl.value)) { | ||
isParsable = false | ||
@@ -391,3 +524,3 @@ return false | ||
let normalized = normalize(value) | ||
let normalized = normalize(value, { property }) | ||
@@ -398,5 +531,7 @@ if (!isParsableCssValue(property, normalized)) { | ||
let sort = context.offsets.arbitraryProperty(classCandidate) | ||
return [ | ||
[ | ||
{ sort: context.arbitraryPropertiesSort, layer: 'utilities' }, | ||
{ sort, layer: 'utilities', options: { respectImportant: true } }, | ||
() => ({ | ||
@@ -453,3 +588,3 @@ [asClass(classCandidate)]: { | ||
return Array.from(splitAtTopLevelOnly(input, separator)) | ||
return splitAtTopLevelOnly(input, separator) | ||
} | ||
@@ -459,3 +594,7 @@ | ||
for (const match of matches) { | ||
match[1].raws.tailwind = { ...match[1].raws.tailwind, classCandidate } | ||
match[1].raws.tailwind = { | ||
...match[1].raws.tailwind, | ||
classCandidate, | ||
preserveSource: match[0].options?.preserveSource ?? false, | ||
} | ||
@@ -516,3 +655,15 @@ yield match | ||
if (matchesPerPlugin.length > 0) { | ||
typesByMatches.set(matchesPerPlugin, sort.options?.type) | ||
let matchingTypes = Array.from( | ||
getMatchingTypes( | ||
sort.options?.types ?? [], | ||
modifier, | ||
sort.options ?? {}, | ||
context.tailwindConfig | ||
) | ||
).map(([_, type]) => type) | ||
if (matchingTypes.length > 0) { | ||
typesByMatches.set(matchesPerPlugin, matchingTypes) | ||
} | ||
matches.push(matchesPerPlugin) | ||
@@ -523,58 +674,111 @@ } | ||
if (isArbitraryValue(modifier)) { | ||
// When generated arbitrary values are ambiguous, we can't know | ||
// which to pick so don't generate any utilities for them | ||
if (matches.length > 1) { | ||
let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])])) | ||
// Partition plugins in 2 categories so that we can start searching in the plugins that | ||
// don't have `any` as a type first. | ||
let [withAny, withoutAny] = matches.reduce( | ||
(group, plugin) => { | ||
let hasAnyType = plugin.some(([{ options }]) => | ||
options.types.some(({ type }) => type === 'any') | ||
) | ||
// Remove duplicates, so that we can detect proper unique types for each plugin. | ||
for (let pluginTypes of typesPerPlugin) { | ||
for (let type of pluginTypes) { | ||
let removeFromOwnGroup = false | ||
if (hasAnyType) { | ||
group[0].push(plugin) | ||
} else { | ||
group[1].push(plugin) | ||
} | ||
return group | ||
}, | ||
[[], []] | ||
) | ||
for (let otherGroup of typesPerPlugin) { | ||
if (pluginTypes === otherGroup) continue | ||
function findFallback(matches) { | ||
// If only a single plugin matches, let's take that one | ||
if (matches.length === 1) { | ||
return matches[0] | ||
} | ||
if (otherGroup.has(type)) { | ||
otherGroup.delete(type) | ||
removeFromOwnGroup = true | ||
// Otherwise, find the plugin that creates a valid rule given the arbitrary value, and | ||
// also has the correct type which preferOnConflicts the plugin in case of clashes. | ||
return matches.find((rules) => { | ||
let matchingTypes = typesByMatches.get(rules) | ||
return rules.some(([{ options }, rule]) => { | ||
if (!isParsableNode(rule)) { | ||
return false | ||
} | ||
} | ||
if (removeFromOwnGroup) pluginTypes.delete(type) | ||
} | ||
return options.types.some( | ||
({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict | ||
) | ||
}) | ||
}) | ||
} | ||
let messages = [] | ||
// Try to find a fallback plugin, because we already know that multiple plugins matched for | ||
// the given arbitrary value. | ||
let fallback = findFallback(withoutAny) ?? findFallback(withAny) | ||
if (fallback) { | ||
matches = [fallback] | ||
} | ||
for (let [idx, group] of typesPerPlugin.entries()) { | ||
for (let type of group) { | ||
let rules = matches[idx] | ||
.map(([, rule]) => rule) | ||
.flat() | ||
.map((rule) => | ||
rule | ||
.toString() | ||
.split('\n') | ||
.slice(1, -1) // Remove selector and closing '}' | ||
.map((line) => line.trim()) | ||
.map((x) => ` ${x}`) // Re-indent | ||
.join('\n') | ||
// We couldn't find a fallback plugin which means that there are now multiple plugins that | ||
// generated css for the current candidate. This means that the result is ambiguous and this | ||
// should not happen. We won't generate anything right now, so let's report this to the user | ||
// by logging some options about what they can do. | ||
else { | ||
let typesPerPlugin = matches.map( | ||
(match) => new Set([...(typesByMatches.get(match) ?? [])]) | ||
) | ||
// Remove duplicates, so that we can detect proper unique types for each plugin. | ||
for (let pluginTypes of typesPerPlugin) { | ||
for (let type of pluginTypes) { | ||
let removeFromOwnGroup = false | ||
for (let otherGroup of typesPerPlugin) { | ||
if (pluginTypes === otherGroup) continue | ||
if (otherGroup.has(type)) { | ||
otherGroup.delete(type) | ||
removeFromOwnGroup = true | ||
} | ||
} | ||
if (removeFromOwnGroup) pluginTypes.delete(type) | ||
} | ||
} | ||
let messages = [] | ||
for (let [idx, group] of typesPerPlugin.entries()) { | ||
for (let type of group) { | ||
let rules = matches[idx] | ||
.map(([, rule]) => rule) | ||
.flat() | ||
.map((rule) => | ||
rule | ||
.toString() | ||
.split('\n') | ||
.slice(1, -1) // Remove selector and closing '}' | ||
.map((line) => line.trim()) | ||
.map((x) => ` ${x}`) // Re-indent | ||
.join('\n') | ||
) | ||
.join('\n\n') | ||
messages.push( | ||
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\`` | ||
) | ||
.join('\n\n') | ||
break | ||
} | ||
} | ||
messages.push( | ||
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\`` | ||
) | ||
break | ||
} | ||
log.warn([ | ||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`, | ||
...messages, | ||
`If this is content and not a class, replace it with \`${candidate | ||
.replace('[', '[') | ||
.replace(']', ']')}\` to silence this warning.`, | ||
]) | ||
continue | ||
} | ||
log.warn([ | ||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`, | ||
...messages, | ||
`If this is content and not a class, replace it with \`${candidate | ||
.replace('[', '[') | ||
.replace(']', ']')}\` to silence this warning.`, | ||
]) | ||
continue | ||
} | ||
@@ -601,15 +805,9 @@ | ||
// Apply final format selector | ||
if (match[0].collectedFormats) { | ||
let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats) | ||
let container = postcss.root({ nodes: [match[1].clone()] }) | ||
container.walkRules((rule) => { | ||
if (inKeyframes(rule)) return | ||
match = applyFinalFormat(match, { context, candidate }) | ||
rule.selector = finalizeSelector(finalFormat, { | ||
selector: rule.selector, | ||
candidate, | ||
context, | ||
}) | ||
}) | ||
match[1] = container.nodes[0] | ||
// Skip rules with invalid selectors | ||
// This will cause the candidate to be added to the "not class" | ||
// cache skipping it entirely for future builds | ||
if (match === null) { | ||
continue | ||
} | ||
@@ -622,2 +820,72 @@ | ||
function applyFinalFormat(match, { context, candidate }) { | ||
if (!match[0].collectedFormats) { | ||
return match | ||
} | ||
let isValid = true | ||
let finalFormat | ||
try { | ||
finalFormat = formatVariantSelector(match[0].collectedFormats, { | ||
context, | ||
candidate, | ||
}) | ||
} catch { | ||
// The format selector we produced is invalid | ||
// This could be because: | ||
// - A bug exists | ||
// - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`) | ||
// - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`) | ||
// Either way the build will fail because of this | ||
// We would rather that the build pass "silently" given that this could | ||
// happen because of picking up invalid things when scanning content | ||
// So we'll throw out the candidate instead | ||
return null | ||
} | ||
let container = postcss.root({ nodes: [match[1].clone()] }) | ||
container.walkRules((rule) => { | ||
if (inKeyframes(rule)) { | ||
return | ||
} | ||
try { | ||
let selector = finalizeSelector(rule.selector, finalFormat, { | ||
candidate, | ||
context, | ||
}) | ||
// Finalize Selector determined that this candidate is irrelevant | ||
// TODO: This elimination should happen earlier so this never happens | ||
if (selector === null) { | ||
rule.remove() | ||
return | ||
} | ||
rule.selector = selector | ||
} catch { | ||
// If this selector is invalid we also want to skip it | ||
// But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content | ||
isValid = false | ||
return false | ||
} | ||
}) | ||
if (!isValid) { | ||
return null | ||
} | ||
// If all rules have been eliminated we can skip this candidate entirely | ||
if (container.nodes.length === 0) { | ||
return null | ||
} | ||
match[1] = container.nodes[0] | ||
return match | ||
} | ||
function inKeyframes(rule) { | ||
@@ -627,4 +895,33 @@ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes' | ||
function generateRules(candidates, context) { | ||
function getImportantStrategy(important) { | ||
if (important === true) { | ||
return (rule) => { | ||
if (inKeyframes(rule)) { | ||
return | ||
} | ||
rule.walkDecls((d) => { | ||
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) { | ||
d.important = true | ||
} | ||
}) | ||
} | ||
} | ||
if (typeof important === 'string') { | ||
return (rule) => { | ||
if (inKeyframes(rule)) { | ||
return | ||
} | ||
rule.selectors = rule.selectors.map((selector) => { | ||
return applyImportantSelector(selector, important) | ||
}) | ||
} | ||
} | ||
} | ||
function generateRules(candidates, context, isSorting = false) { | ||
let allRules = [] | ||
let strategy = getImportantStrategy(context.tailwindConfig.important) | ||
@@ -636,4 +933,4 @@ for (let candidate of candidates) { | ||
if (context.classCache.has(candidate)) { | ||
allRules.push(context.classCache.get(candidate)) | ||
if (context.candidateRuleCache.has(candidate)) { | ||
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate))) | ||
continue | ||
@@ -650,43 +947,25 @@ } | ||
context.classCache.set(candidate, matches) | ||
allRules.push(matches) | ||
} | ||
// Strategy based on `tailwindConfig.important` | ||
let strategy = ((important) => { | ||
if (important === true) { | ||
return (rule) => { | ||
rule.walkDecls((d) => { | ||
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) { | ||
d.important = true | ||
} | ||
}) | ||
} | ||
} | ||
let rules = context.candidateRuleCache.get(candidate) ?? new Set() | ||
context.candidateRuleCache.set(candidate, rules) | ||
if (typeof important === 'string') { | ||
return (rule) => { | ||
rule.selectors = rule.selectors.map((selector) => { | ||
return `${important} ${selector}` | ||
}) | ||
} | ||
} | ||
})(context.tailwindConfig.important) | ||
for (const match of matches) { | ||
let [{ sort, options }, rule] = match | ||
return allRules.flat(1).map(([{ sort, layer, options }, rule]) => { | ||
if (options.respectImportant) { | ||
if (strategy) { | ||
if (options.respectImportant && strategy) { | ||
let container = postcss.root({ nodes: [rule.clone()] }) | ||
container.walkRules((r) => { | ||
if (inKeyframes(r)) { | ||
return | ||
} | ||
strategy(r) | ||
}) | ||
container.walkRules(strategy) | ||
rule = container.nodes[0] | ||
} | ||
// Note: We have to clone rules during sorting | ||
// so we eliminate some shared mutable state | ||
let newEntry = [sort, isSorting ? rule.clone() : rule] | ||
rules.add(newEntry) | ||
context.ruleCache.add(newEntry) | ||
allRules.push(newEntry) | ||
} | ||
} | ||
return [sort | context.layerOrder[layer], rule] | ||
}) | ||
return allRules | ||
} | ||
@@ -693,0 +972,0 @@ |
import fs from 'fs' | ||
import path from 'path' | ||
import resolve from 'resolve' | ||
import detective from 'detective' | ||
function createModule(file) { | ||
const source = fs.readFileSync(file, 'utf-8') | ||
const requires = detective(source) | ||
let jsExtensions = ['.js', '.cjs', '.mjs'] | ||
return { file, requires } | ||
// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve | ||
// `b.ts` before `b.js` | ||
// | ||
// E.g.: | ||
// | ||
// a.ts | ||
// b // .ts | ||
// c // .ts | ||
// a.js | ||
// b // .js or .ts | ||
let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx'] | ||
let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx'] | ||
function resolveWithExtension(file, extensions) { | ||
// Try to find `./a.ts`, `./a.ts`, ... from `./a` | ||
for (let ext of extensions) { | ||
let full = `${file}${ext}` | ||
if (fs.existsSync(full) && fs.statSync(full).isFile()) { | ||
return full | ||
} | ||
} | ||
// Try to find `./a/index.js` from `./a` | ||
for (let ext of extensions) { | ||
let full = `${file}/index${ext}` | ||
if (fs.existsSync(full)) { | ||
return full | ||
} | ||
} | ||
return null | ||
} | ||
export default function getModuleDependencies(entryFile) { | ||
const rootModule = createModule(entryFile) | ||
const modules = [rootModule] | ||
function* _getModuleDependencies(filename, base, seen, ext = path.extname(filename)) { | ||
// Try to find the file | ||
let absoluteFile = resolveWithExtension( | ||
path.resolve(base, filename), | ||
jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder | ||
) | ||
if (absoluteFile === null) return // File doesn't exist | ||
// Iterate over the modules, even when new | ||
// ones are being added | ||
for (const mdl of modules) { | ||
mdl.requires | ||
.filter((dep) => { | ||
// Only track local modules, not node_modules | ||
return dep.startsWith('./') || dep.startsWith('../') | ||
}) | ||
.forEach((dep) => { | ||
try { | ||
const basedir = path.dirname(mdl.file) | ||
const depPath = resolve.sync(dep, { basedir }) | ||
const depModule = createModule(depPath) | ||
// Prevent infinite loops when there are circular dependencies | ||
if (seen.has(absoluteFile)) return // Already seen | ||
seen.add(absoluteFile) | ||
modules.push(depModule) | ||
} catch (_err) { | ||
// eslint-disable-next-line no-empty | ||
} | ||
}) | ||
// Mark the file as a dependency | ||
yield absoluteFile | ||
// Resolve new base for new imports/requires | ||
base = path.dirname(absoluteFile) | ||
ext = path.extname(absoluteFile) | ||
let contents = fs.readFileSync(absoluteFile, 'utf-8') | ||
// Find imports/requires | ||
for (let match of [ | ||
...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi), | ||
...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi), | ||
...contents.matchAll(/require\(['"`](.+)['"`]\)/gi), | ||
]) { | ||
// Bail out if it's not a relative file | ||
if (!match[1].startsWith('.')) continue | ||
yield* _getModuleDependencies(match[1], base, seen, ext) | ||
} | ||
} | ||
return modules | ||
export default function getModuleDependencies(absoluteFilePath) { | ||
if (absoluteFilePath === null) return new Set() | ||
return new Set( | ||
_getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set()) | ||
) | ||
} |
@@ -94,54 +94,75 @@ import postcss from 'postcss' | ||
for (let universal of universals) { | ||
/** @type {Map<string, Set<string>>} */ | ||
let selectorGroups = new Map() | ||
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) { | ||
for (let universal of universals) { | ||
/** @type {Map<string, Set<string>>} */ | ||
let selectorGroups = new Map() | ||
let rules = variableNodeMap.get(universal.params) ?? [] | ||
let rules = variableNodeMap.get(universal.params) ?? [] | ||
for (let rule of rules) { | ||
for (let selector of extractElementSelector(rule.selector)) { | ||
// If selector contains a vendor prefix after a pseudo element or class, | ||
// we consider them separately because merging the declarations into | ||
// a single rule will cause browsers that do not understand the | ||
// vendor prefix to throw out the whole rule | ||
let selectorGroupName = | ||
selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__' | ||
for (let rule of rules) { | ||
for (let selector of extractElementSelector(rule.selector)) { | ||
// If selector contains a vendor prefix after a pseudo element or class, | ||
// we consider them separately because merging the declarations into | ||
// a single rule will cause browsers that do not understand the | ||
// vendor prefix to throw out the whole rule | ||
// Additionally if a selector contains `:has` we also consider | ||
// it separately because FF only recently gained support for it | ||
let selectorGroupName = | ||
selector.includes(':-') || selector.includes('::-') || selector.includes(':has') | ||
? selector | ||
: '__DEFAULT__' | ||
let selectors = selectorGroups.get(selectorGroupName) ?? new Set() | ||
selectorGroups.set(selectorGroupName, selectors) | ||
let selectors = selectorGroups.get(selectorGroupName) ?? new Set() | ||
selectorGroups.set(selectorGroupName, selectors) | ||
selectors.add(selector) | ||
selectors.add(selector) | ||
} | ||
} | ||
} | ||
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) { | ||
if (selectorGroups.size === 0) { | ||
universal.remove() | ||
continue | ||
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) { | ||
if (selectorGroups.size === 0) { | ||
universal.remove() | ||
continue | ||
} | ||
for (let [, selectors] of selectorGroups) { | ||
let universalRule = postcss.rule({ | ||
source: universal.source, | ||
}) | ||
universalRule.selectors = [...selectors] | ||
universalRule.append(universal.nodes.map((node) => node.clone())) | ||
universal.before(universalRule) | ||
} | ||
} | ||
for (let [, selectors] of selectorGroups) { | ||
let universalRule = postcss.rule({ | ||
source: universal.source, | ||
}) | ||
universal.remove() | ||
} | ||
} else if (universals.size) { | ||
let universalRule = postcss.rule({ | ||
selectors: ['*', '::before', '::after'], | ||
}) | ||
universalRule.selectors = [...selectors] | ||
for (let universal of universals) { | ||
universalRule.append(universal.nodes) | ||
universalRule.append(universal.nodes.map((node) => node.clone())) | ||
if (!universalRule.parent) { | ||
universal.before(universalRule) | ||
} | ||
} else { | ||
let universalRule = postcss.rule({ | ||
source: universal.source, | ||
}) | ||
universalRule.selectors = ['*', '::before', '::after'] | ||
if (!universalRule.source) { | ||
universalRule.source = universal.source | ||
} | ||
universalRule.append(universal.nodes) | ||
universal.before(universalRule) | ||
universal.remove() | ||
} | ||
universal.remove() | ||
let backdropRule = universalRule.clone({ | ||
selectors: ['::backdrop'], | ||
}) | ||
universalRule.after(backdropRule) | ||
} | ||
} | ||
} |
@@ -14,3 +14,2 @@ import fs from 'fs' | ||
import { coerceValue } from '../util/pluginUtils' | ||
import bigSign from '../util/bigSign' | ||
import { variantPlugins, corePlugins } from '../corePlugins' | ||
@@ -22,8 +21,21 @@ import * as sharedState from './sharedState' | ||
import negateValue from '../util/negateValue' | ||
import isValidArbitraryValue from '../util/isValidArbitraryValue' | ||
import { generateRules } from './generateRules' | ||
import isSyntacticallyValidPropertyValue from '../util/isSyntacticallyValidPropertyValue' | ||
import { generateRules, getClassNameFromSelector } from './generateRules' | ||
import { hasContentChanged } from './cacheInvalidation.js' | ||
import { Offsets } from './offsets.js' | ||
import { flagEnabled } from '../featureFlags.js' | ||
import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector' | ||
let MATCH_VARIANT = Symbol() | ||
export const INTERNAL_FEATURES = Symbol() | ||
const VARIANT_TYPES = { | ||
AddVariant: Symbol.for('ADD_VARIANT'), | ||
MatchVariant: Symbol.for('MATCH_VARIANT'), | ||
} | ||
const VARIANT_INFO = { | ||
Base: 1 << 0, | ||
Dynamic: 1 << 1, | ||
} | ||
function prefix(context, selector) { | ||
@@ -34,29 +46,61 @@ let prefix = context.tailwindConfig.prefix | ||
function parseVariantFormatString(input) { | ||
if (input.includes('{')) { | ||
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`) | ||
function normalizeOptionTypes({ type = 'any', ...options }) { | ||
let types = [].concat(type) | ||
return input | ||
.split(/{(.*)}/gim) | ||
.flatMap((line) => parseVariantFormatString(line)) | ||
.filter(Boolean) | ||
return { | ||
...options, | ||
types: types.map((type) => { | ||
if (Array.isArray(type)) { | ||
return { type: type[0], ...type[1] } | ||
} | ||
return { type, preferOnConflict: false } | ||
}), | ||
} | ||
return [input.trim()] | ||
} | ||
function isBalanced(input) { | ||
let count = 0 | ||
function parseVariantFormatString(input) { | ||
/** @type {string[]} */ | ||
let parts = [] | ||
for (let char of input) { | ||
if (char === '{') { | ||
count++ | ||
// When parsing whitespace around special characters are insignificant | ||
// However, _inside_ of a variant they could be | ||
// Because the selector could look like this | ||
// @media { &[data-name="foo bar"] } | ||
// This is why we do not skip whitespace | ||
let current = '' | ||
let depth = 0 | ||
for (let idx = 0; idx < input.length; idx++) { | ||
let char = input[idx] | ||
if (char === '\\') { | ||
// Escaped characters are not special | ||
current += '\\' + input[++idx] | ||
} else if (char === '{') { | ||
// Nested rule: start | ||
++depth | ||
parts.push(current.trim()) | ||
current = '' | ||
} else if (char === '}') { | ||
if (--count < 0) { | ||
return false // unbalanced | ||
// Nested rule: end | ||
if (--depth < 0) { | ||
throw new Error(`Your { and } are unbalanced.`) | ||
} | ||
parts.push(current.trim()) | ||
current = '' | ||
} else { | ||
// Normal character | ||
current += char | ||
} | ||
} | ||
return count === 0 | ||
if (current.length > 0) { | ||
parts.push(current.trim()) | ||
} | ||
parts = parts.filter((part) => part !== '') | ||
return parts | ||
} | ||
@@ -110,39 +154,41 @@ | ||
/** | ||
* Ignore everything inside a :not(...). This allows you to write code like | ||
* `div:not(.foo)`. If `.foo` is never found in your code, then we used to | ||
* not generated it. But now we will ignore everything inside a `:not`, so | ||
* that it still gets generated. | ||
* | ||
* @param {selectorParser.Root} selectors | ||
*/ | ||
function ignoreNot(selectors) { | ||
selectors.walkPseudos((pseudo) => { | ||
if (pseudo.value === ':not') { | ||
pseudo.remove() | ||
} | ||
}) | ||
} | ||
function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) { | ||
let classes = [] | ||
let selectors = [] | ||
// Handle normal rules | ||
if (node.type === 'rule') { | ||
// Ignore everything inside a :not(...). This allows you to write code like | ||
// `div:not(.foo)`. If `.foo` is never found in your code, then we used to | ||
// not generated it. But now we will ignore everything inside a `:not`, so | ||
// that it still gets generated. | ||
function ignoreNot(selectors) { | ||
selectors.walkPseudos((pseudo) => { | ||
if (pseudo.value === ':not') { | ||
pseudo.remove() | ||
} | ||
}) | ||
} | ||
// Handle normal rules | ||
selectors.push(...node.selectors) | ||
} else if (node.type === 'atrule') { | ||
// Handle at-rules (which contains nested rules) | ||
node.walkRules((rule) => selectors.push(...rule.selectors)) | ||
} | ||
for (let selector of node.selectors) { | ||
let classCandidates = getClasses(selector, ignoreNot) | ||
// At least one of the selectors contains non-"on-demandable" candidates. | ||
if (classCandidates.length === 0) { | ||
state.containsNonOnDemandable = true | ||
} | ||
for (let selector of selectors) { | ||
let classCandidates = getClasses(selector, ignoreNot) | ||
for (let classCandidate of classCandidates) { | ||
classes.push(classCandidate) | ||
} | ||
// At least one of the selectors contains non-"on-demandable" candidates. | ||
if (classCandidates.length === 0) { | ||
state.containsNonOnDemandable = true | ||
} | ||
} | ||
// Handle at-rules (which contains nested rules) | ||
else if (node.type === 'atrule') { | ||
node.walkRules((rule) => { | ||
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) { | ||
classes.push(classCandidate) | ||
} | ||
}) | ||
for (let classCandidate of classCandidates) { | ||
classes.push(classCandidate) | ||
} | ||
} | ||
@@ -195,4 +241,4 @@ | ||
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str) | ||
return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() })) | ||
let [, name, params] = /@(\S*)( .+|[({].*)?/g.exec(str) | ||
return ({ wrap }) => wrap(postcss.atRule({ name, params: params?.trim() ?? '' })) | ||
}) | ||
@@ -208,2 +254,9 @@ .reverse() | ||
/** | ||
* | ||
* @param {any} tailwindConfig | ||
* @param {any} context | ||
* @param {object} param2 | ||
* @param {Offsets} param2.offsets | ||
*/ | ||
function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) { | ||
@@ -230,37 +283,10 @@ function getConfigValue(path, defaultValue) { | ||
function resolveThemeValue(path, defaultValue, opts = {}) { | ||
let parts = toPath(path) | ||
let value = getConfigValue(['theme', ...parts], defaultValue) | ||
return transformThemeValue(parts[0])(value, opts) | ||
} | ||
let variantIdentifier = 0 | ||
let api = { | ||
addVariant(variantName, variantFunctions, options = {}) { | ||
variantFunctions = [].concat(variantFunctions).map((variantFunction) => { | ||
if (typeof variantFunction !== 'string') { | ||
// Safelist public API functions | ||
return ({ args, modifySelectors, container, separator, wrap, format }) => { | ||
let result = variantFunction( | ||
Object.assign( | ||
{ modifySelectors, container, separator }, | ||
variantFunction[MATCH_VARIANT] && { args, wrap, format } | ||
) | ||
) | ||
if (typeof result === 'string' && !isValidVariantFormatString(result)) { | ||
throw new Error( | ||
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.` | ||
) | ||
} | ||
return result | ||
} | ||
} | ||
if (!isValidVariantFormatString(variantFunction)) { | ||
throw new Error( | ||
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.` | ||
) | ||
} | ||
return parseVariant(variantFunction) | ||
}) | ||
insertInto(variantList, variantName, options) | ||
variantMap.set(variantName, variantFunctions) | ||
}, | ||
postcss, | ||
@@ -270,7 +296,3 @@ prefix: applyConfiguredPrefix, | ||
config: getConfigValue, | ||
theme(path, defaultValue) { | ||
const [pathRoot, ...subPaths] = toPath(path) | ||
const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue) | ||
return transformThemeValue(pathRoot)(value) | ||
}, | ||
theme: resolveThemeValue, | ||
corePlugins: (path) => { | ||
@@ -290,3 +312,3 @@ if (Array.isArray(tailwindConfig.corePlugins)) { | ||
let prefixedIdentifier = prefixIdentifier(identifier, {}) | ||
let offset = offsets.base++ | ||
let offset = offsets.create('base') | ||
@@ -320,3 +342,3 @@ if (!context.candidateRuleMap.has(prefixedIdentifier)) { | ||
.get(prefixedIdentifier) | ||
.push([{ sort: offsets.base++, layer: 'defaults' }, rule]) | ||
.push([{ sort: offsets.create('defaults'), layer: 'defaults' }, rule]) | ||
} | ||
@@ -326,2 +348,3 @@ }, | ||
let defaultOptions = { | ||
preserveSource: false, | ||
respectPrefix: true, | ||
@@ -344,3 +367,3 @@ respectImportant: false, | ||
.get(prefixedIdentifier) | ||
.push([{ sort: offsets.components++, layer: 'components', options }, rule]) | ||
.push([{ sort: offsets.create('components'), layer: 'components', options }, rule]) | ||
} | ||
@@ -350,2 +373,3 @@ }, | ||
let defaultOptions = { | ||
preserveSource: false, | ||
respectPrefix: true, | ||
@@ -368,3 +392,3 @@ respectImportant: true, | ||
.get(prefixedIdentifier) | ||
.push([{ sort: offsets.utilities++, layer: 'utilities', options }, rule]) | ||
.push([{ sort: offsets.create('utilities'), layer: 'utilities', options }, rule]) | ||
} | ||
@@ -376,7 +400,8 @@ }, | ||
respectImportant: true, | ||
modifiers: false, | ||
} | ||
options = { ...defaultOptions, ...options } | ||
options = normalizeOptionTypes({ ...defaultOptions, ...options }) | ||
let offset = offsets.utilities++ | ||
let offset = offsets.create('utilities') | ||
@@ -390,5 +415,8 @@ for (let identifier in utilities) { | ||
function wrapped(modifier, { isOnlyPlugin }) { | ||
let { type = 'any' } = options | ||
type = [].concat(type) | ||
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig) | ||
let [value, coercedType, utilityModifier] = coerceValue( | ||
options.types, | ||
modifier, | ||
options, | ||
tailwindConfig | ||
) | ||
@@ -399,12 +427,36 @@ if (value === undefined) { | ||
if (!type.includes(coercedType) && !isOnlyPlugin) { | ||
return [] | ||
if (!options.types.some(({ type }) => type === coercedType)) { | ||
if (isOnlyPlugin) { | ||
log.warn([ | ||
`Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`, | ||
`You can safely update it to \`${identifier}-${modifier.replace( | ||
coercedType + ':', | ||
'' | ||
)}\`.`, | ||
]) | ||
} else { | ||
return [] | ||
} | ||
} | ||
if (!isValidArbitraryValue(value)) { | ||
if (!isSyntacticallyValidPropertyValue(value)) { | ||
return [] | ||
} | ||
let extras = { | ||
get modifier() { | ||
if (!options.modifiers) { | ||
log.warn(`modifier-used-without-options-for-${identifier}`, [ | ||
'Your plugin must set `modifiers: true` in its options to support modifiers.', | ||
]) | ||
} | ||
return utilityModifier | ||
}, | ||
} | ||
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers') | ||
let ruleSets = [] | ||
.concat(rule(value)) | ||
.concat(modifiersEnabled ? rule(value, extras) : rule(value)) | ||
.filter(Boolean) | ||
@@ -431,7 +483,8 @@ .map((declaration) => ({ | ||
respectImportant: false, | ||
modifiers: false, | ||
} | ||
options = { ...defaultOptions, ...options } | ||
options = normalizeOptionTypes({ ...defaultOptions, ...options }) | ||
let offset = offsets.components++ | ||
let offset = offsets.create('components') | ||
@@ -445,5 +498,8 @@ for (let identifier in components) { | ||
function wrapped(modifier, { isOnlyPlugin }) { | ||
let { type = 'any' } = options | ||
type = [].concat(type) | ||
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig) | ||
let [value, coercedType, utilityModifier] = coerceValue( | ||
options.types, | ||
modifier, | ||
options, | ||
tailwindConfig | ||
) | ||
@@ -454,3 +510,3 @@ if (value === undefined) { | ||
if (!type.includes(coercedType)) { | ||
if (!options.types.some(({ type }) => type === coercedType)) { | ||
if (isOnlyPlugin) { | ||
@@ -469,8 +525,22 @@ log.warn([ | ||
if (!isValidArbitraryValue(value)) { | ||
if (!isSyntacticallyValidPropertyValue(value)) { | ||
return [] | ||
} | ||
let extras = { | ||
get modifier() { | ||
if (!options.modifiers) { | ||
log.warn(`modifier-used-without-options-for-${identifier}`, [ | ||
'Your plugin must set `modifiers: true` in its options to support modifiers.', | ||
]) | ||
} | ||
return utilityModifier | ||
}, | ||
} | ||
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers') | ||
let ruleSets = [] | ||
.concat(rule(value)) | ||
.concat(modifiersEnabled ? rule(value, extras) : rule(value)) | ||
.filter(Boolean) | ||
@@ -493,27 +563,101 @@ .map((declaration) => ({ | ||
}, | ||
matchVariant: function (variants, options) { | ||
for (let variant in variants) { | ||
for (let [k, v] of Object.entries(options?.values ?? {})) { | ||
api.addVariant(`${variant}-${k}`, variants[variant](v)) | ||
addVariant(variantName, variantFunctions, options = {}) { | ||
variantFunctions = [].concat(variantFunctions).map((variantFunction) => { | ||
if (typeof variantFunction !== 'string') { | ||
// Safelist public API functions | ||
return (api = {}) => { | ||
let { args, modifySelectors, container, separator, wrap, format } = api | ||
let result = variantFunction( | ||
Object.assign( | ||
{ modifySelectors, container, separator }, | ||
options.type === VARIANT_TYPES.MatchVariant && { args, wrap, format } | ||
) | ||
) | ||
if (typeof result === 'string' && !isValidVariantFormatString(result)) { | ||
throw new Error( | ||
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.` | ||
) | ||
} | ||
if (Array.isArray(result)) { | ||
return result | ||
.filter((variant) => typeof variant === 'string') | ||
.map((variant) => parseVariant(variant)) | ||
} | ||
// result may be undefined with legacy variants that use APIs like `modifySelectors` | ||
// result may also be a postcss node if someone was returning the result from `modifySelectors` | ||
return result && typeof result === 'string' && parseVariant(result)(api) | ||
} | ||
} | ||
if (!isValidVariantFormatString(variantFunction)) { | ||
throw new Error( | ||
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.` | ||
) | ||
} | ||
return parseVariant(variantFunction) | ||
}) | ||
insertInto(variantList, variantName, options) | ||
variantMap.set(variantName, variantFunctions) | ||
context.variantOptions.set(variantName, options) | ||
}, | ||
matchVariant(variant, variantFn, options) { | ||
// A unique identifier that "groups" these variants together. | ||
// This is for internal use only which is why it is not present in the types | ||
let id = options?.id ?? ++variantIdentifier | ||
let isSpecial = variant === '@' | ||
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers') | ||
for (let [key, value] of Object.entries(options?.values ?? {})) { | ||
if (key === 'DEFAULT') continue | ||
api.addVariant( | ||
variant, | ||
Object.assign( | ||
({ args, wrap }) => { | ||
let formatString = variants[variant](args) | ||
if (!formatString) return null | ||
isSpecial ? `${variant}${key}` : `${variant}-${key}`, | ||
({ args, container }) => { | ||
return variantFn( | ||
value, | ||
modifiersEnabled ? { modifier: args?.modifier, container } : { container } | ||
) | ||
}, | ||
if (!formatString.startsWith('@')) { | ||
return formatString | ||
} | ||
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(formatString) | ||
return wrap(postcss.atRule({ name, params: params.trim() })) | ||
}, | ||
{ [MATCH_VARIANT]: true } | ||
), | ||
options | ||
{ | ||
...options, | ||
value, | ||
id, | ||
type: VARIANT_TYPES.MatchVariant, | ||
variantInfo: VARIANT_INFO.Base, | ||
} | ||
) | ||
} | ||
let hasDefault = 'DEFAULT' in (options?.values ?? {}) | ||
api.addVariant( | ||
variant, | ||
({ args, container }) => { | ||
if (args?.value === sharedState.NONE && !hasDefault) { | ||
return null | ||
} | ||
return variantFn( | ||
args?.value === sharedState.NONE | ||
? options.values.DEFAULT | ||
: // Falling back to args if it is a string, otherwise '' for older intellisense | ||
// (JetBrains) plugins. | ||
args?.value ?? (typeof args === 'string' ? args : ''), | ||
modifiersEnabled ? { modifier: args?.modifier, container } : { container } | ||
) | ||
}, | ||
{ | ||
...options, | ||
id, | ||
type: VARIANT_TYPES.MatchVariant, | ||
variantInfo: VARIANT_INFO.Dynamic, | ||
} | ||
) | ||
}, | ||
@@ -535,2 +679,3 @@ } | ||
let changed = false | ||
let mtimesToCommit = new Map() | ||
@@ -556,6 +701,6 @@ for (let file of files) { | ||
fileModifiedMap.set(file, newModified) | ||
mtimesToCommit.set(file, newModified) | ||
} | ||
return changed | ||
return [changed, mtimesToCommit] | ||
} | ||
@@ -597,3 +742,3 @@ | ||
layerPlugins.push(function ({ addComponents }) { | ||
addComponents(node, { respectPrefix: false }) | ||
addComponents(node, { respectPrefix: false, preserveSource: true }) | ||
}) | ||
@@ -605,3 +750,3 @@ } | ||
layerPlugins.push(function ({ addUtilities }) { | ||
addUtilities(node, { respectPrefix: false }) | ||
addUtilities(node, { respectPrefix: false, preserveSource: true }) | ||
}) | ||
@@ -640,14 +785,42 @@ } | ||
let beforeVariants = [ | ||
variantPlugins['childVariant'], | ||
variantPlugins['pseudoElementVariants'], | ||
variantPlugins['pseudoClassVariants'], | ||
variantPlugins['hasVariants'], | ||
variantPlugins['ariaVariants'], | ||
variantPlugins['dataVariants'], | ||
] | ||
let afterVariants = [ | ||
variantPlugins['supportsVariants'], | ||
variantPlugins['reducedMotionVariants'], | ||
variantPlugins['prefersContrastVariants'], | ||
variantPlugins['screenVariants'], | ||
variantPlugins['orientationVariants'], | ||
variantPlugins['directionVariants'], | ||
variantPlugins['reducedMotionVariants'], | ||
variantPlugins['darkVariants'], | ||
variantPlugins['forcedColorsVariants'], | ||
variantPlugins['printVariant'], | ||
variantPlugins['screenVariants'], | ||
variantPlugins['orientationVariants'], | ||
] | ||
// This is a compatibility fix for the pre 3.4 dark mode behavior | ||
// `class` retains the old behavior, but `selector` keeps the new behavior | ||
let isLegacyDarkMode = | ||
context.tailwindConfig.darkMode === 'class' || | ||
(Array.isArray(context.tailwindConfig.darkMode) && | ||
context.tailwindConfig.darkMode[0] === 'class') | ||
if (isLegacyDarkMode) { | ||
afterVariants = [ | ||
variantPlugins['supportsVariants'], | ||
variantPlugins['reducedMotionVariants'], | ||
variantPlugins['prefersContrastVariants'], | ||
variantPlugins['darkVariants'], | ||
variantPlugins['screenVariants'], | ||
variantPlugins['orientationVariants'], | ||
variantPlugins['directionVariants'], | ||
variantPlugins['forcedColorsVariants'], | ||
variantPlugins['printVariant'], | ||
] | ||
} | ||
return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins] | ||
@@ -659,10 +832,7 @@ } | ||
let variantMap = new Map() | ||
let offsets = { | ||
defaults: 0n, | ||
base: 0n, | ||
components: 0n, | ||
utilities: 0n, | ||
user: 0n, | ||
} | ||
context.variantMap = variantMap | ||
let offsets = new Offsets() | ||
context.offsets = offsets | ||
let classList = new Set() | ||
@@ -687,45 +857,13 @@ | ||
let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([ | ||
offsets.base, | ||
offsets.defaults, | ||
offsets.components, | ||
offsets.utilities, | ||
offsets.user, | ||
]) | ||
let reservedBits = BigInt(highestOffset.toString(2).length) | ||
// Make sure to record bit masks for every variant | ||
offsets.recordVariants(variantList, (variant) => variantMap.get(variant).length) | ||
// A number one less than the top range of the highest offset area | ||
// so arbitrary properties are always sorted at the end. | ||
context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n | ||
context.layerOrder = { | ||
defaults: (1n << reservedBits) << 0n, | ||
base: (1n << reservedBits) << 1n, | ||
components: (1n << reservedBits) << 2n, | ||
utilities: (1n << reservedBits) << 3n, | ||
user: (1n << reservedBits) << 4n, | ||
} | ||
reservedBits += 5n | ||
let offset = 0 | ||
context.variantOrder = new Map( | ||
variantList | ||
.map((variant, i) => { | ||
let variantFunctions = variantMap.get(variant).length | ||
let bits = (1n << BigInt(i + offset)) << reservedBits | ||
offset += variantFunctions - 1 | ||
return [variant, bits] | ||
}) | ||
.sort(([, a], [, z]) => bigSign(a - z)) | ||
) | ||
context.minimumScreen = [...context.variantOrder.values()].shift() | ||
// Build variantMap | ||
for (let [variantName, variantFunctions] of variantMap.entries()) { | ||
let sort = context.variantOrder.get(variantName) | ||
context.variantMap.set( | ||
variantName, | ||
variantFunctions.map((variantFunction, idx) => [sort << BigInt(idx), variantFunction]) | ||
variantFunctions.map((variantFunction, idx) => [ | ||
offsets.forVariant(variantName, idx), | ||
variantFunction, | ||
]) | ||
) | ||
@@ -759,2 +897,3 @@ } | ||
let prefixLength = context.tailwindConfig.prefix.length | ||
let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!')) | ||
@@ -785,2 +924,17 @@ for (let util of classList) { | ||
if (options.types.some(({ type }) => type === 'color')) { | ||
classes = [ | ||
...classes, | ||
...classes.flatMap((cls) => | ||
Object.keys(context.tailwindConfig.theme.opacity).map( | ||
(opacity) => `${cls}/${opacity}` | ||
) | ||
), | ||
] | ||
} | ||
if (checkImportantUtils && options?.respectImportant) { | ||
classes = [...classes, ...classes.map((cls) => '!' + cls)] | ||
} | ||
return classes | ||
@@ -827,12 +981,37 @@ })() | ||
let darkClassName = [].concat(context.tailwindConfig.darkMode ?? 'media')[1] ?? 'dark' | ||
// A list of utilities that are used by certain Tailwind CSS utilities but | ||
// that don't exist on their own. This will result in them "not existing" and | ||
// sorting could be weird since you still require them in order to make the | ||
// host utitlies work properly. (Thanks Biology) | ||
let parasiteUtilities = new Set([prefix(context, 'group'), prefix(context, 'peer')]) | ||
// host utilities work properly. (Thanks Biology) | ||
let parasiteUtilities = [ | ||
prefix(context, darkClassName), | ||
prefix(context, 'group'), | ||
prefix(context, 'peer'), | ||
] | ||
context.getClassOrder = function getClassOrder(classes) { | ||
let sortedClassNames = new Map() | ||
for (let [sort, rule] of generateRules(new Set(classes), context)) { | ||
if (sortedClassNames.has(rule.raws.tailwind.candidate)) continue | ||
sortedClassNames.set(rule.raws.tailwind.candidate, sort) | ||
// Sort classes so they're ordered in a deterministic manner | ||
let sorted = [...classes].sort((a, z) => { | ||
if (a === z) return 0 | ||
if (a < z) return -1 | ||
return 1 | ||
}) | ||
// Non-util classes won't be generated, so we default them to null | ||
let sortedClassNames = new Map(sorted.map((className) => [className, null])) | ||
// Sort all classes in order | ||
// Non-tailwind classes won't be generated and will be left as `null` | ||
let rules = generateRules(new Set(sorted), context, true) | ||
rules = context.offsets.sort(rules) | ||
let idx = BigInt(parasiteUtilities.length) | ||
for (const [, rule] of rules) { | ||
let candidate = rule.raws.tailwind.candidate | ||
// When multiple rules match a candidate | ||
// always take the position of the first one | ||
sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++) | ||
} | ||
@@ -842,8 +1021,9 @@ | ||
let order = sortedClassNames.get(className) ?? null | ||
let parasiteIndex = parasiteUtilities.indexOf(className) | ||
if (order === null && parasiteUtilities.has(className)) { | ||
if (order === null && parasiteIndex !== -1) { | ||
// This will make sure that it is at the very beginning of the | ||
// `components` layer which technically means 'before any | ||
// components'. | ||
order = context.layerOrder.components | ||
order = BigInt(parasiteIndex) | ||
} | ||
@@ -857,3 +1037,3 @@ | ||
// ['uppercase', 'lowercase', ...] | ||
context.getClassList = function getClassList() { | ||
context.getClassList = function getClassList(options = {}) { | ||
let output = [] | ||
@@ -863,10 +1043,27 @@ | ||
if (Array.isArray(util)) { | ||
let [utilName, options] = util | ||
let [utilName, utilOptions] = util | ||
let negativeClasses = [] | ||
for (let [key, value] of Object.entries(options?.values ?? {})) { | ||
output.push(formatClass(utilName, key)) | ||
if (options?.supportsNegativeValues && negateValue(value)) { | ||
negativeClasses.push(formatClass(utilName, `-${key}`)) | ||
let modifiers = Object.keys(utilOptions?.modifiers ?? {}) | ||
if (utilOptions?.types?.some(({ type }) => type === 'color')) { | ||
modifiers.push(...Object.keys(context.tailwindConfig.theme.opacity ?? {})) | ||
} | ||
let metadata = { modifiers } | ||
let includeMetadata = options.includeMetadata && modifiers.length > 0 | ||
for (let [key, value] of Object.entries(utilOptions?.values ?? {})) { | ||
// Ignore undefined and null values | ||
if (value == null) { | ||
continue | ||
} | ||
let cls = formatClass(utilName, key) | ||
output.push(includeMetadata ? [cls, metadata] : cls) | ||
if (utilOptions?.supportsNegativeValues && negateValue(value)) { | ||
let cls = formatClass(utilName, `-${key}`) | ||
negativeClasses.push(includeMetadata ? [cls, metadata] : cls) | ||
} | ||
} | ||
@@ -882,4 +1079,212 @@ | ||
} | ||
// Generate a list of available variants with meta information of the type of variant. | ||
context.getVariants = function getVariants() { | ||
// We use a unique, random ID for candidate names to avoid conflicts | ||
// We can't use characters like `_`, `:`, `@` or `.` because they might | ||
// be used as a separator | ||
let id = Math.random().toString(36).substring(7).toUpperCase() | ||
let result = [] | ||
for (let [name, options] of context.variantOptions.entries()) { | ||
if (options.variantInfo === VARIANT_INFO.Base) continue | ||
result.push({ | ||
name, | ||
isArbitrary: options.type === Symbol.for('MATCH_VARIANT'), | ||
values: Object.keys(options.values ?? {}), | ||
hasDash: name !== '@', | ||
selectors({ modifier, value } = {}) { | ||
let candidate = `TAILWINDPLACEHOLDER${id}` | ||
let rule = postcss.rule({ selector: `.${candidate}` }) | ||
let container = postcss.root({ nodes: [rule.clone()] }) | ||
let before = container.toString() | ||
let fns = (context.variantMap.get(name) ?? []).flatMap(([_, fn]) => fn) | ||
let formatStrings = [] | ||
for (let fn of fns) { | ||
let localFormatStrings = [] | ||
let api = { | ||
args: { modifier, value: options.values?.[value] ?? value }, | ||
separator: context.tailwindConfig.separator, | ||
modifySelectors(modifierFunction) { | ||
// Run the modifierFunction over each rule | ||
container.each((rule) => { | ||
if (rule.type !== 'rule') { | ||
return | ||
} | ||
rule.selectors = rule.selectors.map((selector) => { | ||
return modifierFunction({ | ||
get className() { | ||
return getClassNameFromSelector(selector) | ||
}, | ||
selector, | ||
}) | ||
}) | ||
}) | ||
return container | ||
}, | ||
format(str) { | ||
localFormatStrings.push(str) | ||
}, | ||
wrap(wrapper) { | ||
localFormatStrings.push(`@${wrapper.name} ${wrapper.params} { & }`) | ||
}, | ||
container, | ||
} | ||
let ruleWithVariant = fn(api) | ||
if (localFormatStrings.length > 0) { | ||
formatStrings.push(localFormatStrings) | ||
} | ||
if (Array.isArray(ruleWithVariant)) { | ||
for (let variantFunction of ruleWithVariant) { | ||
localFormatStrings = [] | ||
variantFunction(api) | ||
formatStrings.push(localFormatStrings) | ||
} | ||
} | ||
} | ||
// Reverse engineer the result of the `container` | ||
let manualFormatStrings = [] | ||
let after = container.toString() | ||
if (before !== after) { | ||
// Figure out all selectors | ||
container.walkRules((rule) => { | ||
let modified = rule.selector | ||
// Rebuild the base selector, this is what plugin authors would do | ||
// as well. E.g.: `${variant}${separator}${className}`. | ||
// However, plugin authors probably also prepend or append certain | ||
// classes, pseudos, ids, ... | ||
let rebuiltBase = selectorParser((selectors) => { | ||
selectors.walkClasses((classNode) => { | ||
classNode.value = `${name}${context.tailwindConfig.separator}${classNode.value}` | ||
}) | ||
}).processSync(modified) | ||
// Now that we know the original selector, the new selector, and | ||
// the rebuild part in between, we can replace the part that plugin | ||
// authors need to rebuild with `&`, and eventually store it in the | ||
// collectedFormats. Similar to what `format('...')` would do. | ||
// | ||
// E.g.: | ||
// variant: foo | ||
// selector: .markdown > p | ||
// modified (by plugin): .foo .foo\\:markdown > p | ||
// rebuiltBase (internal): .foo\\:markdown > p | ||
// format: .foo & | ||
manualFormatStrings.push(modified.replace(rebuiltBase, '&').replace(candidate, '&')) | ||
}) | ||
// Figure out all atrules | ||
container.walkAtRules((atrule) => { | ||
manualFormatStrings.push(`@${atrule.name} (${atrule.params}) { & }`) | ||
}) | ||
} | ||
let isArbitraryVariant = !(value in (options.values ?? {})) | ||
let internalFeatures = options[INTERNAL_FEATURES] ?? {} | ||
let respectPrefix = (() => { | ||
if (isArbitraryVariant) return false | ||
if (internalFeatures.respectPrefix === false) return false | ||
return true | ||
})() | ||
formatStrings = formatStrings.map((format) => | ||
format.map((str) => ({ | ||
format: str, | ||
respectPrefix, | ||
})) | ||
) | ||
manualFormatStrings = manualFormatStrings.map((format) => ({ | ||
format, | ||
respectPrefix, | ||
})) | ||
let opts = { | ||
candidate, | ||
context, | ||
} | ||
let result = formatStrings.map((formats) => | ||
finalizeSelector(`.${candidate}`, formatVariantSelector(formats, opts), opts) | ||
.replace(`.${candidate}`, '&') | ||
.replace('{ & }', '') | ||
.trim() | ||
) | ||
if (manualFormatStrings.length > 0) { | ||
result.push( | ||
formatVariantSelector(manualFormatStrings, opts) | ||
.toString() | ||
.replace(`.${candidate}`, '&') | ||
) | ||
} | ||
return result | ||
}, | ||
}) | ||
} | ||
return result | ||
} | ||
} | ||
/** | ||
* Mark as class as retroactively invalid | ||
* | ||
* | ||
* @param {string} candidate | ||
*/ | ||
function markInvalidUtilityCandidate(context, candidate) { | ||
if (!context.classCache.has(candidate)) { | ||
return | ||
} | ||
// Mark this as not being a real utility | ||
context.notClassCache.add(candidate) | ||
// Remove it from any candidate-specific caches | ||
context.classCache.delete(candidate) | ||
context.applyClassCache.delete(candidate) | ||
context.candidateRuleMap.delete(candidate) | ||
context.candidateRuleCache.delete(candidate) | ||
// Ensure the stylesheet gets rebuilt | ||
context.stylesheetCache = null | ||
} | ||
/** | ||
* Mark as class as retroactively invalid | ||
* | ||
* @param {import('postcss').Node} node | ||
*/ | ||
function markInvalidUtilityNode(context, node) { | ||
let candidate = node.raws.tailwind.candidate | ||
if (!candidate) { | ||
return | ||
} | ||
for (const entry of context.ruleCache) { | ||
if (entry[1].raws.tailwind.candidate === candidate) { | ||
context.ruleCache.delete(entry) | ||
// context.postCssNodeCache.delete(node) | ||
} | ||
} | ||
markInvalidUtilityCandidate(context, candidate) | ||
} | ||
export function createContext(tailwindConfig, changedContent = [], root = postcss.root()) { | ||
@@ -889,5 +1294,7 @@ let context = { | ||
ruleCache: new Set(), | ||
candidateRuleCache: new Map(), | ||
classCache: new Map(), | ||
applyClassCache: new Map(), | ||
notClassCache: new Set(), | ||
// Seed the not class cache with the blocklist (which is only strings) | ||
notClassCache: new Set(tailwindConfig.blocklist ?? []), | ||
postCssNodeCache: new Map(), | ||
@@ -899,2 +1306,6 @@ candidateRuleMap: new Map(), | ||
stylesheetCache: null, | ||
variantOptions: new Map(), | ||
markInvalidUtilityCandidate: (candidate) => markInvalidUtilityCandidate(context, candidate), | ||
markInvalidUtilityNode: (node) => markInvalidUtilityNode(context, node), | ||
} | ||
@@ -942,3 +1353,3 @@ | ||
if (existingContext) { | ||
let contextDependenciesChanged = trackModified( | ||
let [contextDependenciesChanged, mtimesToCommit] = trackModified( | ||
[...contextDependencies], | ||
@@ -948,3 +1359,3 @@ getFileModifiedMap(existingContext) | ||
if (!contextDependenciesChanged && !cssDidChange) { | ||
return [existingContext, false] | ||
return [existingContext, false, mtimesToCommit] | ||
} | ||
@@ -980,4 +1391,8 @@ } | ||
trackModified([...contextDependencies], getFileModifiedMap(context)) | ||
Object.assign(context, { | ||
userConfigPath, | ||
}) | ||
let [, mtimesToCommit] = trackModified([...contextDependencies], getFileModifiedMap(context)) | ||
// --- | ||
@@ -996,3 +1411,3 @@ | ||
return [context, true] | ||
return [context, true, mtimesToCommit] | ||
} |
@@ -0,20 +1,15 @@ | ||
// @ts-check | ||
import fs from 'fs' | ||
import path from 'path' | ||
import LRU from '@alloc/quick-lru' | ||
import fastGlob from 'fast-glob' | ||
import LRU from 'quick-lru' | ||
import normalizePath from 'normalize-path' | ||
import hash from '../util/hashConfig' | ||
import getModuleDependencies from '../lib/getModuleDependencies' | ||
import resolveConfig from '../public/resolve-config' | ||
import resolveConfigPath from '../util/resolveConfigPath' | ||
import { env } from './sharedState' | ||
import { getContext, getFileModifiedMap } from './setupContextUtils' | ||
import parseDependency from '../util/parseDependency' | ||
import { validateConfig } from '../util/validateConfig.js' | ||
import { parseCandidateFiles, resolvedChangedContent } from './content.js' | ||
import { loadConfig } from '../lib/load-config' | ||
import getModuleDependencies from './getModuleDependencies' | ||
@@ -30,5 +25,3 @@ let configPathCache = new LRU({ maxSize: 100 }) | ||
let candidateFiles = tailwindConfig.content.files | ||
.filter((item) => typeof item === 'string') | ||
.map((contentPath) => normalizePath(contentPath)) | ||
let candidateFiles = parseCandidateFiles(context, tailwindConfig) | ||
@@ -46,3 +39,3 @@ return candidateFilesCache.set(context, candidateFiles).get(context) | ||
let newDeps = getModuleDependencies(userConfigPath).map((dep) => dep.file) | ||
let newDeps = getModuleDependencies(userConfigPath) | ||
@@ -68,4 +61,3 @@ let modified = false | ||
} | ||
let newConfig = resolveConfig(require(userConfigPath)) | ||
newConfig = validateConfig(newConfig) | ||
let newConfig = validateConfig(resolveConfig(loadConfig(userConfigPath))) | ||
let newHash = hash(newConfig) | ||
@@ -77,5 +69,3 @@ configPathCache.set(userConfigPath, [newConfig, newHash, newDeps, newModified]) | ||
// It's a plain object, not a path | ||
let newConfig = resolveConfig( | ||
configOrPath.config === undefined ? configOrPath : configOrPath.config | ||
) | ||
let newConfig = resolveConfig(configOrPath?.config ?? configOrPath ?? {}) | ||
@@ -87,32 +77,2 @@ newConfig = validateConfig(newConfig) | ||
function resolvedChangedContent(context, candidateFiles, fileModifiedMap) { | ||
let changedContent = context.tailwindConfig.content.files | ||
.filter((item) => typeof item.raw === 'string') | ||
.map(({ raw, extension = 'html' }) => ({ content: raw, extension })) | ||
for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) { | ||
let content = fs.readFileSync(changedFile, 'utf8') | ||
let extension = path.extname(changedFile).slice(1) | ||
changedContent.push({ content, extension }) | ||
} | ||
return changedContent | ||
} | ||
function resolveChangedFiles(candidateFiles, fileModifiedMap) { | ||
let changedFiles = new Set() | ||
env.DEBUG && console.time('Finding changed files') | ||
let files = fastGlob.sync(candidateFiles) | ||
for (let file of files) { | ||
let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity | ||
let modified = fs.statSync(file).mtimeMs | ||
if (modified > prevModified) { | ||
changedFiles.add(file) | ||
fileModifiedMap.set(file, modified) | ||
} | ||
} | ||
env.DEBUG && console.timeEnd('Finding changed files') | ||
return changedFiles | ||
} | ||
// DISABLE_TOUCH = TRUE | ||
@@ -149,3 +109,3 @@ | ||
let [context] = getContext( | ||
let [context, , mTimesToCommit] = getContext( | ||
root, | ||
@@ -159,2 +119,4 @@ result, | ||
let fileModifiedMap = getFileModifiedMap(context) | ||
let candidateFiles = getCandidateFiles(context, tailwindConfig) | ||
@@ -168,8 +130,5 @@ | ||
if (tailwindDirectives.size > 0) { | ||
let fileModifiedMap = getFileModifiedMap(context) | ||
// Add template paths as postcss dependencies. | ||
for (let fileOrGlob of candidateFiles) { | ||
let dependency = parseDependency(fileOrGlob) | ||
if (dependency) { | ||
for (let contentPath of candidateFiles) { | ||
for (let dependency of parseDependency(contentPath)) { | ||
registerDependency(dependency) | ||
@@ -179,9 +138,25 @@ } | ||
for (let changedContent of resolvedChangedContent( | ||
let [changedContent, contentMTimesToCommit] = resolvedChangedContent( | ||
context, | ||
candidateFiles, | ||
fileModifiedMap | ||
)) { | ||
context.changedContent.push(changedContent) | ||
) | ||
for (let content of changedContent) { | ||
context.changedContent.push(content) | ||
} | ||
// Add the mtimes of the content files to the commit list | ||
// We can overwrite the existing values because unconditionally | ||
// This is because: | ||
// 1. Most of the files here won't be in the map yet | ||
// 2. If they are that means it's a context dependency | ||
// and we're reading this after the context. This means | ||
// that the mtime we just read is strictly >= the context | ||
// mtime. Unless the user / os is doing something weird | ||
// in which the mtime would be going backwards. If that | ||
// happens there's already going to be problems. | ||
for (let [path, mtime] of contentMTimesToCommit.entries()) { | ||
mTimesToCommit.set(path, mtime) | ||
} | ||
} | ||
@@ -193,2 +168,9 @@ | ||
// "commit" the new modified time for all context deps | ||
// We do this here because we want content tracking to | ||
// read the "old" mtime even when it's a context dependency. | ||
for (let [path, mtime] of mTimesToCommit.entries()) { | ||
fileModifiedMap.set(path, mtime) | ||
} | ||
return context | ||
@@ -195,0 +177,0 @@ } |
@@ -1,5 +0,12 @@ | ||
export const env = { | ||
NODE_ENV: process.env.NODE_ENV, | ||
DEBUG: resolveDebug(process.env.DEBUG), | ||
} | ||
export const env = | ||
typeof process !== 'undefined' | ||
? { | ||
NODE_ENV: process.env.NODE_ENV, | ||
DEBUG: resolveDebug(process.env.DEBUG), | ||
} | ||
: { | ||
NODE_ENV: 'production', | ||
DEBUG: false, | ||
} | ||
export const contextMap = new Map() | ||
@@ -11,2 +18,4 @@ export const configContextMap = new Map() | ||
export const NONE = Symbol('__NONE__') | ||
export function resolveDebug(debug) { | ||
@@ -13,0 +22,0 @@ if (debug === undefined) { |
@@ -10,3 +10,2 @@ import normalizeTailwindDirectives from './lib/normalizeTailwindDirectives' | ||
import partitionApplyAtRules from './lib/partitionApplyAtRules' | ||
import detectNesting from './lib/detectNesting' | ||
import { createContext } from './lib/setupContextUtils' | ||
@@ -16,7 +15,5 @@ import { issueFlagNotices } from './featureFlags' | ||
export default function processTailwindFeatures(setupContext) { | ||
return function (root, result) { | ||
return async function (root, result) { | ||
let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root) | ||
detectNesting()(root, result) | ||
// Partition apply rules that are found in the css | ||
@@ -49,3 +46,4 @@ // itself. | ||
expandTailwindAtRules(context)(root, result) | ||
await expandTailwindAtRules(context)(root, result) | ||
// Partition apply rules that are generated by | ||
@@ -52,0 +50,0 @@ // addComponents, addUtilities and so on. |
@@ -27,2 +27,3 @@ import log from '../util/log' | ||
900: '#0f172a', | ||
950: '#020617', | ||
}, | ||
@@ -40,2 +41,3 @@ gray: { | ||
900: '#111827', | ||
950: '#030712', | ||
}, | ||
@@ -53,2 +55,3 @@ zinc: { | ||
900: '#18181b', | ||
950: '#09090b', | ||
}, | ||
@@ -66,2 +69,3 @@ neutral: { | ||
900: '#171717', | ||
950: '#0a0a0a', | ||
}, | ||
@@ -79,2 +83,3 @@ stone: { | ||
900: '#1c1917', | ||
950: '#0c0a09', | ||
}, | ||
@@ -92,2 +97,3 @@ red: { | ||
900: '#7f1d1d', | ||
950: '#450a0a', | ||
}, | ||
@@ -105,2 +111,3 @@ orange: { | ||
900: '#7c2d12', | ||
950: '#431407', | ||
}, | ||
@@ -118,2 +125,3 @@ amber: { | ||
900: '#78350f', | ||
950: '#451a03', | ||
}, | ||
@@ -131,2 +139,3 @@ yellow: { | ||
900: '#713f12', | ||
950: '#422006', | ||
}, | ||
@@ -144,2 +153,3 @@ lime: { | ||
900: '#365314', | ||
950: '#1a2e05', | ||
}, | ||
@@ -157,2 +167,3 @@ green: { | ||
900: '#14532d', | ||
950: '#052e16', | ||
}, | ||
@@ -170,2 +181,3 @@ emerald: { | ||
900: '#064e3b', | ||
950: '#022c22', | ||
}, | ||
@@ -183,2 +195,3 @@ teal: { | ||
900: '#134e4a', | ||
950: '#042f2e', | ||
}, | ||
@@ -196,2 +209,3 @@ cyan: { | ||
900: '#164e63', | ||
950: '#083344', | ||
}, | ||
@@ -209,2 +223,3 @@ sky: { | ||
900: '#0c4a6e', | ||
950: '#082f49', | ||
}, | ||
@@ -222,2 +237,3 @@ blue: { | ||
900: '#1e3a8a', | ||
950: '#172554', | ||
}, | ||
@@ -235,2 +251,3 @@ indigo: { | ||
900: '#312e81', | ||
950: '#1e1b4b', | ||
}, | ||
@@ -248,2 +265,3 @@ violet: { | ||
900: '#4c1d95', | ||
950: '#2e1065', | ||
}, | ||
@@ -261,2 +279,3 @@ purple: { | ||
900: '#581c87', | ||
950: '#3b0764', | ||
}, | ||
@@ -274,2 +293,3 @@ fuchsia: { | ||
900: '#701a75', | ||
950: '#4a044e', | ||
}, | ||
@@ -287,2 +307,3 @@ pink: { | ||
900: '#831843', | ||
950: '#500724', | ||
}, | ||
@@ -300,2 +321,3 @@ rose: { | ||
900: '#881337', | ||
950: '#4c0519', | ||
}, | ||
@@ -302,0 +324,0 @@ get lightBlue() { |
import { cloneDeep } from '../util/cloneDeep' | ||
import defaultConfig from '../../stubs/defaultConfig.stub' | ||
import defaultConfig from '../../stubs/config.full' | ||
export default cloneDeep(defaultConfig) |
import { cloneDeep } from '../util/cloneDeep' | ||
import defaultConfig from '../../stubs/defaultConfig.stub' | ||
import defaultFullConfig from '../../stubs/config.full' | ||
export default cloneDeep(defaultConfig.theme) | ||
export default cloneDeep(defaultFullConfig.theme) |
@@ -5,4 +5,4 @@ export default function buildMediaQuery(screens) { | ||
return screens | ||
.map((screen) => | ||
screen.values.map((screen) => { | ||
.map((screen) => { | ||
let values = screen.values.map((screen) => { | ||
if (screen.raw !== undefined) { | ||
@@ -19,4 +19,6 @@ return screen.raw | ||
}) | ||
) | ||
return screen.not ? `not all and ${values}` : values | ||
}) | ||
.join(', ') | ||
} |
@@ -0,1 +1,7 @@ | ||
/** | ||
* @param {import('postcss').Container[]} nodes | ||
* @param {any} source | ||
* @param {any} raws | ||
* @returns {import('postcss').Container[]} | ||
*/ | ||
export default function cloneNodes(nodes, source = undefined, raws = undefined) { | ||
@@ -5,12 +11,2 @@ return nodes.map((node) => { | ||
if (source !== undefined) { | ||
cloned.source = source | ||
if ('walk' in cloned) { | ||
cloned.walk((child) => { | ||
child.source = source | ||
}) | ||
} | ||
} | ||
if (raws !== undefined) { | ||
@@ -23,4 +19,33 @@ cloned.raws.tailwind = { | ||
if (source !== undefined) { | ||
traverse(cloned, (node) => { | ||
// Do not traverse nodes that have opted | ||
// to preserve their original source | ||
let shouldPreserveSource = node.raws.tailwind?.preserveSource === true && node.source | ||
if (shouldPreserveSource) { | ||
return false | ||
} | ||
// Otherwise we can safely replace the source | ||
// And continue traversing | ||
node.source = source | ||
}) | ||
} | ||
return cloned | ||
}) | ||
} | ||
/** | ||
* Traverse a tree of nodes and don't traverse children if the callback | ||
* returns false. Ideally we'd use Container#walk instead of this | ||
* function but it stops traversing siblings too. | ||
* | ||
* @param {import('postcss').Container} node | ||
* @param {(node: import('postcss').Container) => boolean} onNode | ||
*/ | ||
function traverse(node, onNode) { | ||
if (onNode(node) !== false) { | ||
node.each?.((child) => traverse(child, onNode)) | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import namedColors from 'color-name' | ||
import namedColors from './colorNames' | ||
@@ -8,9 +8,9 @@ let HEX = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i | ||
let ALPHA_SEP = /\s*[,/]\s*/ | ||
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/ | ||
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/ | ||
let RGB = new RegExp( | ||
`^(rgb)a?\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$` | ||
`^(rgba?)\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$` | ||
) | ||
let HSL = new RegExp( | ||
`^(hsl)a?\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$` | ||
`^(hsla?)\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$` | ||
) | ||
@@ -56,2 +56,12 @@ | ||
// rgba(var(--my-color), 0.1) | ||
// hsla(var(--my-color), 0.1) | ||
if (color.length === 2 && color[0].startsWith('var(')) { | ||
return { | ||
mode: match[1], | ||
color: [color[0]], | ||
alpha: color[1], | ||
} | ||
} | ||
if (!loose && color.length !== 3) { | ||
@@ -74,3 +84,8 @@ return null | ||
let hasAlpha = alpha !== undefined | ||
if (mode === 'rgba' || mode === 'hsla') { | ||
return `${mode}(${color.join(', ')}${hasAlpha ? `, ${alpha}` : ''})` | ||
} | ||
return `${mode}(${color.join(' ')}${hasAlpha ? ` / ${alpha}` : ''})` | ||
} |
import { parseColor } from './color' | ||
import { parseBoxShadowValue } from './parseBoxShadowValue' | ||
import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' | ||
@@ -8,8 +9,43 @@ let cssFunctions = ['min', 'max', 'clamp', 'calc'] | ||
let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. | ||
let UNDERSCORE = /_(?![^(]*\))/g // Underscore separator that is not located between brackets. E.g.: `rgba(255,_255,_255)_black` these don't count. | ||
function isCSSFunction(value) { | ||
return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value)) | ||
} | ||
// These properties accept a `<dashed-ident>` as one of the values. This means that you can use them | ||
// as: `timeline-scope: --tl;` | ||
// | ||
// Without the `var(--tl)`, in these cases we don't want to normalize the value, and you should add | ||
// the `var()` yourself. | ||
// | ||
// More info: | ||
// - https://drafts.csswg.org/scroll-animations/#propdef-timeline-scope | ||
// - https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope#dashed-ident | ||
// - https://www.w3.org/TR/css-anchor-position-1 | ||
// | ||
const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([ | ||
// Concrete properties | ||
'scroll-timeline-name', | ||
'timeline-scope', | ||
'view-timeline-name', | ||
'font-palette', | ||
'anchor-name', | ||
'anchor-scope', | ||
'position-anchor', | ||
'position-try-options', | ||
// Shorthand properties | ||
'scroll-timeline', | ||
'animation-timeline', | ||
'view-timeline', | ||
'position-try', | ||
]) | ||
// This is not a data type, but rather a function that can normalize the | ||
// correct values. | ||
export function normalize(value, isRoot = true) { | ||
export function normalize(value, context = null, isRoot = true) { | ||
let isVarException = context && AUTO_VAR_INJECTION_EXCEPTIONS.has(context.property) | ||
if (value.startsWith('--') && !isVarException) { | ||
return `var(${value})` | ||
} | ||
// Keep raw strings if it starts with `url(` | ||
@@ -25,3 +61,3 @@ if (value.includes('url(')) { | ||
return normalize(part, false) | ||
return normalize(part, context, false) | ||
}) | ||
@@ -45,16 +81,149 @@ .join('') | ||
// Add spaces around operators inside calc() that do not follow an operator | ||
// or '('. | ||
value = value.replace(/calc\(.+\)/g, (match) => { | ||
return match.replace( | ||
/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, | ||
'$1 $2 ' | ||
) | ||
}) | ||
value = normalizeMathOperatorSpacing(value) | ||
// Add spaces around some operators not inside calc() that do not follow an operator | ||
// or '('. | ||
return value.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([\/])/g, '$1 $2 ') | ||
return value | ||
} | ||
export function normalizeAttributeSelectors(value) { | ||
// Wrap values in attribute selectors with quotes | ||
if (value.includes('=')) { | ||
value = value.replace(/(=.*)/g, (_fullMatch, match) => { | ||
if (match[1] === "'" || match[1] === '"') { | ||
return match | ||
} | ||
// Handle regex flags on unescaped values | ||
if (match.length > 2) { | ||
let trailingCharacter = match[match.length - 1] | ||
if ( | ||
match[match.length - 2] === ' ' && | ||
(trailingCharacter === 'i' || | ||
trailingCharacter === 'I' || | ||
trailingCharacter === 's' || | ||
trailingCharacter === 'S') | ||
) { | ||
return `="${match.slice(1, -2)}" ${match[match.length - 1]}` | ||
} | ||
} | ||
return `="${match.slice(1)}"` | ||
}) | ||
} | ||
return value | ||
} | ||
/** | ||
* Add spaces around operators inside math functions | ||
* like calc() that do not follow an operator, '(', or `,`. | ||
* | ||
* @param {string} value | ||
* @returns {string} | ||
*/ | ||
function normalizeMathOperatorSpacing(value) { | ||
let preventFormattingInFunctions = ['theme'] | ||
let preventFormattingKeywords = [ | ||
'min-content', | ||
'max-content', | ||
'fit-content', | ||
// Env | ||
'safe-area-inset-top', | ||
'safe-area-inset-right', | ||
'safe-area-inset-bottom', | ||
'safe-area-inset-left', | ||
'titlebar-area-x', | ||
'titlebar-area-y', | ||
'titlebar-area-width', | ||
'titlebar-area-height', | ||
'keyboard-inset-top', | ||
'keyboard-inset-right', | ||
'keyboard-inset-bottom', | ||
'keyboard-inset-left', | ||
'keyboard-inset-width', | ||
'keyboard-inset-height', | ||
'radial-gradient', | ||
'linear-gradient', | ||
'conic-gradient', | ||
'repeating-radial-gradient', | ||
'repeating-linear-gradient', | ||
'repeating-conic-gradient', | ||
] | ||
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => { | ||
let result = '' | ||
function lastChar() { | ||
let char = result.trimEnd() | ||
return char[char.length - 1] | ||
} | ||
for (let i = 0; i < match.length; i++) { | ||
function peek(word) { | ||
return word.split('').every((char, j) => match[i + j] === char) | ||
} | ||
function consumeUntil(chars) { | ||
let minIndex = Infinity | ||
for (let char of chars) { | ||
let index = match.indexOf(char, i) | ||
if (index !== -1 && index < minIndex) { | ||
minIndex = index | ||
} | ||
} | ||
let result = match.slice(i, minIndex) | ||
i += result.length - 1 | ||
return result | ||
} | ||
let char = match[i] | ||
// Handle `var(--variable)` | ||
if (peek('var')) { | ||
// When we consume until `)`, then we are dealing with this scenario: | ||
// `var(--example)` | ||
// | ||
// When we consume until `,`, then we are dealing with this scenario: | ||
// `var(--example, 1rem)` | ||
// | ||
// In this case we do want to "format", the default value as well | ||
result += consumeUntil([')', ',']) | ||
} | ||
// Skip formatting of known keywords | ||
else if (preventFormattingKeywords.some((keyword) => peek(keyword))) { | ||
let keyword = preventFormattingKeywords.find((keyword) => peek(keyword)) | ||
result += keyword | ||
i += keyword.length - 1 | ||
} | ||
// Skip formatting inside known functions | ||
else if (preventFormattingInFunctions.some((fn) => peek(fn))) { | ||
result += consumeUntil([')']) | ||
} | ||
// Don't break CSS grid track names | ||
else if (peek('[')) { | ||
result += consumeUntil([']']) | ||
} | ||
// Handle operators | ||
else if ( | ||
['+', '-', '*', '/'].includes(char) && | ||
!['(', '+', '-', '*', '/', ','].includes(lastChar()) | ||
) { | ||
result += ` ${char} ` | ||
} else { | ||
result += char | ||
} | ||
} | ||
// Simplify multiple spaces | ||
return result.replace(/\s+/g, ' ') | ||
}) | ||
} | ||
export function url(value) { | ||
@@ -65,11 +234,12 @@ return value.startsWith('url(') | ||
export function number(value) { | ||
return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value)) | ||
return !isNaN(Number(value)) || isCSSFunction(value) | ||
} | ||
export function percentage(value) { | ||
return value.split(UNDERSCORE).every((part) => { | ||
return /%$/g.test(part) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(part)) | ||
}) | ||
return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value) | ||
} | ||
// Please refer to MDN when updating this list: | ||
// https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units | ||
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units | ||
let lengthUnits = [ | ||
@@ -88,2 +258,3 @@ 'cm', | ||
'lh', | ||
'rlh', | ||
'vw', | ||
@@ -93,12 +264,24 @@ 'vh', | ||
'vmax', | ||
'vb', | ||
'vi', | ||
'svw', | ||
'svh', | ||
'lvw', | ||
'lvh', | ||
'dvw', | ||
'dvh', | ||
'cqw', | ||
'cqh', | ||
'cqi', | ||
'cqb', | ||
'cqmin', | ||
'cqmax', | ||
] | ||
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})` | ||
export function length(value) { | ||
return value.split(UNDERSCORE).every((part) => { | ||
return ( | ||
part === '0' || | ||
new RegExp(`${lengthUnitsPattern}$`).test(part) || | ||
cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part)) | ||
) | ||
}) | ||
return ( | ||
value === '0' || | ||
new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) || | ||
isCSSFunction(value) | ||
) | ||
} | ||
@@ -126,3 +309,3 @@ | ||
let result = value.split(UNDERSCORE).every((part) => { | ||
let result = splitAtTopLevelOnly(value, '_').every((part) => { | ||
part = normalize(part) | ||
@@ -142,3 +325,3 @@ | ||
let images = 0 | ||
let result = value.split(COMMA).every((part) => { | ||
let result = splitAtTopLevelOnly(value, ',').every((part) => { | ||
part = normalize(part) | ||
@@ -164,7 +347,8 @@ | ||
let gradientTypes = new Set([ | ||
'conic-gradient', | ||
'linear-gradient', | ||
'radial-gradient', | ||
'repeating-conic-gradient', | ||
'repeating-linear-gradient', | ||
'repeating-radial-gradient', | ||
'conic-gradient', | ||
]) | ||
@@ -185,3 +369,3 @@ export function gradient(value) { | ||
let positions = 0 | ||
let result = value.split(UNDERSCORE).every((part) => { | ||
let result = splitAtTopLevelOnly(value, '_').every((part) => { | ||
part = normalize(part) | ||
@@ -204,3 +388,3 @@ | ||
let fonts = 0 | ||
let result = value.split(COMMA).every((part) => { | ||
let result = splitAtTopLevelOnly(value, ',').every((part) => { | ||
part = normalize(part) | ||
@@ -257,3 +441,3 @@ | ||
'x-large', | ||
'x-large', | ||
'xx-large', | ||
'xxx-large', | ||
@@ -260,0 +444,0 @@ ]) |
@@ -5,32 +5,152 @@ import selectorParser from 'postcss-selector-parser' | ||
import prefixSelector from '../util/prefixSelector' | ||
import { movePseudos } from './pseudoElements' | ||
import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' | ||
/** @typedef {import('postcss-selector-parser').Root} Root */ | ||
/** @typedef {import('postcss-selector-parser').Selector} Selector */ | ||
/** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */ | ||
/** @typedef {import('postcss-selector-parser').Node} Node */ | ||
/** @typedef {{format: string, respectPrefix: boolean}[]} RawFormats */ | ||
/** @typedef {import('postcss-selector-parser').Root} ParsedFormats */ | ||
/** @typedef {RawFormats | ParsedFormats} AcceptedFormats */ | ||
let MERGE = ':merge' | ||
let PARENT = '&' | ||
export let selectorFunctions = new Set([MERGE]) | ||
/** | ||
* @param {RawFormats} formats | ||
* @param {{context: any, candidate: string, base: string | null}} options | ||
* @returns {ParsedFormats | null} | ||
*/ | ||
export function formatVariantSelector(formats, { context, candidate }) { | ||
let prefix = context?.tailwindConfig.prefix ?? '' | ||
export function formatVariantSelector(current, ...others) { | ||
for (let other of others) { | ||
let incomingValue = resolveFunctionArgument(other, MERGE) | ||
if (incomingValue !== null) { | ||
let existingValue = resolveFunctionArgument(current, MERGE, incomingValue) | ||
if (existingValue !== null) { | ||
let existingTarget = `${MERGE}(${incomingValue})` | ||
let splitIdx = other.indexOf(existingTarget) | ||
let addition = other.slice(splitIdx + existingTarget.length).split(' ')[0] | ||
// Parse the format selector into an AST | ||
let parsedFormats = formats.map((format) => { | ||
let ast = selectorParser().astSync(format.format) | ||
current = current.replace(existingTarget, existingTarget + addition) | ||
continue | ||
} | ||
return { | ||
...format, | ||
ast: format.respectPrefix ? prefixSelector(prefix, ast) : ast, | ||
} | ||
}) | ||
current = other.replace(PARENT, current) | ||
// We start with the candidate selector | ||
let formatAst = selectorParser.root({ | ||
nodes: [ | ||
selectorParser.selector({ | ||
nodes: [selectorParser.className({ value: escapeClassName(candidate) })], | ||
}), | ||
], | ||
}) | ||
// And iteratively merge each format selector into the candidate selector | ||
for (let { ast } of parsedFormats) { | ||
// 1. Handle :merge() special pseudo-class | ||
;[formatAst, ast] = handleMergePseudo(formatAst, ast) | ||
// 2. Merge the format selector into the current selector AST | ||
ast.walkNesting((nesting) => nesting.replaceWith(...formatAst.nodes[0].nodes)) | ||
// 3. Keep going! | ||
formatAst = ast | ||
} | ||
return current | ||
return formatAst | ||
} | ||
export function finalizeSelector(format, { selector, candidate, context }) { | ||
let ast = selectorParser().astSync(selector) | ||
/** | ||
* Given any node in a selector this gets the "simple" selector it's a part of | ||
* A simple selector is just a list of nodes without any combinators | ||
* Technically :is(), :not(), :has(), etc… can have combinators but those are nested | ||
* inside the relevant node and won't be picked up so they're fine to ignore | ||
* | ||
* @param {Node} node | ||
* @returns {Node[]} | ||
**/ | ||
function simpleSelectorForNode(node) { | ||
/** @type {Node[]} */ | ||
let nodes = [] | ||
// Walk backwards until we hit a combinator node (or the start) | ||
while (node.prev() && node.prev().type !== 'combinator') { | ||
node = node.prev() | ||
} | ||
// Now record all non-combinator nodes until we hit one (or the end) | ||
while (node && node.type !== 'combinator') { | ||
nodes.push(node) | ||
node = node.next() | ||
} | ||
return nodes | ||
} | ||
/** | ||
* Resorts the nodes in a selector to ensure they're in the correct order | ||
* Tags go before classes, and pseudo classes go after classes | ||
* | ||
* @param {Selector} sel | ||
* @returns {Selector} | ||
**/ | ||
function resortSelector(sel) { | ||
sel.sort((a, b) => { | ||
if (a.type === 'tag' && b.type === 'class') { | ||
return -1 | ||
} else if (a.type === 'class' && b.type === 'tag') { | ||
return 1 | ||
} else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) { | ||
return -1 | ||
} else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') { | ||
return 1 | ||
} | ||
return sel.index(a) - sel.index(b) | ||
}) | ||
return sel | ||
} | ||
/** | ||
* Remove extraneous selectors that do not include the base class/candidate | ||
* | ||
* Example: | ||
* Given the utility `.a, .b { color: red}` | ||
* Given the candidate `sm:b` | ||
* | ||
* The final selector should be `.sm\:b` and not `.a, .sm\:b` | ||
* | ||
* @param {Selector} ast | ||
* @param {string} base | ||
*/ | ||
export function eliminateIrrelevantSelectors(sel, base) { | ||
let hasClassesMatchingCandidate = false | ||
sel.walk((child) => { | ||
if (child.type === 'class' && child.value === base) { | ||
hasClassesMatchingCandidate = true | ||
return false // Stop walking | ||
} | ||
}) | ||
if (!hasClassesMatchingCandidate) { | ||
sel.remove() | ||
} | ||
// We do NOT recursively eliminate sub selectors that don't have the base class | ||
// as this is NOT a safe operation. For example, if we have: | ||
// `.space-x-2 > :not([hidden]) ~ :not([hidden])` | ||
// We cannot remove the [hidden] from the :not() because it would change the | ||
// meaning of the selector. | ||
// TODO: Can we do this for :matches, :is, and :where? | ||
} | ||
/** | ||
* @param {string} current | ||
* @param {AcceptedFormats} formats | ||
* @param {{context: any, candidate: string, base: string | null}} options | ||
* @returns {string} | ||
*/ | ||
export function finalizeSelector(current, formats, { context, candidate, base }) { | ||
let separator = context?.tailwindConfig?.separator ?? ':' | ||
@@ -45,24 +165,7 @@ | ||
// | ||
let splitter = new RegExp(`\\${separator}(?![^[]*\\])`) | ||
let base = candidate.split(splitter).pop() | ||
base = base ?? splitAtTopLevelOnly(candidate, separator).pop() | ||
if (context?.tailwindConfig?.prefix) { | ||
format = prefixSelector(context.tailwindConfig.prefix, format) | ||
} | ||
// Parse the selector into an AST | ||
let selector = selectorParser().astSync(current) | ||
format = format.replace(PARENT, `.${escapeClassName(candidate)}`) | ||
let formatAst = selectorParser().astSync(format) | ||
// Remove extraneous selectors that do not include the base class/candidate being matched against | ||
// For example if we have a utility defined `.a, .b { color: red}` | ||
// And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b` | ||
ast.each((node) => { | ||
let hasClassesMatchingCandidate = node.some((n) => n.type === 'class' && n.value === base) | ||
if (!hasClassesMatchingCandidate) { | ||
node.remove() | ||
} | ||
}) | ||
// Normalize escaped classes, e.g.: | ||
@@ -79,3 +182,3 @@ // | ||
// | ||
ast.walkClasses((node) => { | ||
selector.walkClasses((node) => { | ||
if (node.raws && node.value.includes(base)) { | ||
@@ -86,126 +189,140 @@ node.raws.value = escapeClassName(unescape(node.raws.value)) | ||
// Remove extraneous selectors that do not include the base candidate | ||
selector.each((sel) => eliminateIrrelevantSelectors(sel, base)) | ||
// If ffter eliminating irrelevant selectors, we end up with nothing | ||
// Then the whole "rule" this is associated with does not need to exist | ||
// We use `null` as a marker value for that case | ||
if (selector.length === 0) { | ||
return null | ||
} | ||
// If there are no formats that means there were no variants added to the candidate | ||
// so we can just return the selector as-is | ||
let formatAst = Array.isArray(formats) | ||
? formatVariantSelector(formats, { context, candidate }) | ||
: formats | ||
if (formatAst === null) { | ||
return selector.toString() | ||
} | ||
let simpleStart = selectorParser.comment({ value: '/*__simple__*/' }) | ||
let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' }) | ||
// We can safely replace the escaped base now, since the `base` section is | ||
// now in a normalized escaped value. | ||
ast.walkClasses((node) => { | ||
if (node.value === base) { | ||
node.replaceWith(...formatAst.nodes) | ||
selector.walkClasses((node) => { | ||
if (node.value !== base) { | ||
return | ||
} | ||
}) | ||
// This will make sure to move pseudo's to the correct spot (the end for | ||
// pseudo elements) because otherwise the selector will never work | ||
// anyway. | ||
// | ||
// E.g.: | ||
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` | ||
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` | ||
// | ||
// `::before:hover` doesn't work, which means that we can make it work for you by flipping the order. | ||
function collectPseudoElements(selector) { | ||
let nodes = [] | ||
let parent = node.parent | ||
let formatNodes = formatAst.nodes[0].nodes | ||
for (let node of selector.nodes) { | ||
if (isPseudoElement(node)) { | ||
nodes.push(node) | ||
selector.removeChild(node) | ||
} | ||
// Perf optimization: if the parent is a single class we can just replace it and be done | ||
if (parent.nodes.length === 1) { | ||
node.replaceWith(...formatNodes) | ||
return | ||
} | ||
if (node?.nodes) { | ||
nodes.push(...collectPseudoElements(node)) | ||
} | ||
let simpleSelector = simpleSelectorForNode(node) | ||
parent.insertBefore(simpleSelector[0], simpleStart) | ||
parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd) | ||
for (let child of formatNodes) { | ||
parent.insertBefore(simpleSelector[0], child.clone()) | ||
} | ||
return nodes | ||
} | ||
node.remove() | ||
// Re-sort the simple selector to ensure it's in the correct order | ||
simpleSelector = simpleSelectorForNode(simpleStart) | ||
let firstNode = parent.index(simpleStart) | ||
parent.nodes.splice( | ||
firstNode, | ||
simpleSelector.length, | ||
...resortSelector(selectorParser.selector({ nodes: simpleSelector })).nodes | ||
) | ||
simpleStart.remove() | ||
simpleEnd.remove() | ||
}) | ||
// Remove unnecessary pseudo selectors that we used as placeholders | ||
ast.each((selector) => { | ||
selector.walkPseudos((p) => { | ||
if (selectorFunctions.has(p.value)) { | ||
p.replaceWith(p.nodes) | ||
} | ||
}) | ||
let pseudoElements = collectPseudoElements(selector) | ||
if (pseudoElements.length > 0) { | ||
selector.nodes.push(pseudoElements.sort(sortSelector)) | ||
selector.walkPseudos((p) => { | ||
if (p.value === MERGE) { | ||
p.replaceWith(p.nodes) | ||
} | ||
}) | ||
return ast.toString() | ||
// Move pseudo elements to the end of the selector (if necessary) | ||
selector.each((sel) => movePseudos(sel)) | ||
return selector.toString() | ||
} | ||
// Note: As a rule, double colons (::) should be used instead of a single colon | ||
// (:). This distinguishes pseudo-classes from pseudo-elements. However, since | ||
// this distinction was not present in older versions of the W3C spec, most | ||
// browsers support both syntaxes for the original pseudo-elements. | ||
let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter'] | ||
/** | ||
* | ||
* @param {Selector} selector | ||
* @param {Selector} format | ||
*/ | ||
export function handleMergePseudo(selector, format) { | ||
/** @type {{pseudo: Pseudo, value: string}[]} */ | ||
let merges = [] | ||
// These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter. | ||
let pseudoElementExceptions = ['::file-selector-button'] | ||
// Find all :merge() pseudo-classes in `selector` | ||
selector.walkPseudos((pseudo) => { | ||
if (pseudo.value === MERGE) { | ||
merges.push({ | ||
pseudo, | ||
value: pseudo.nodes[0].toString(), | ||
}) | ||
} | ||
}) | ||
// This will make sure to move pseudo's to the correct spot (the end for | ||
// pseudo elements) because otherwise the selector will never work | ||
// anyway. | ||
// | ||
// E.g.: | ||
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` | ||
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` | ||
// | ||
// `::before:hover` doesn't work, which means that we can make it work | ||
// for you by flipping the order. | ||
function sortSelector(a, z) { | ||
// Both nodes are non-pseudo's so we can safely ignore them and keep | ||
// them in the same order. | ||
if (a.type !== 'pseudo' && z.type !== 'pseudo') { | ||
return 0 | ||
} | ||
// Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector` | ||
format.walkPseudos((pseudo) => { | ||
if (pseudo.value !== MERGE) { | ||
return | ||
} | ||
// If one of them is a combinator, we need to keep it in the same order | ||
// because that means it will start a new "section" in the selector. | ||
if ((a.type === 'combinator') ^ (z.type === 'combinator')) { | ||
return 0 | ||
} | ||
let value = pseudo.nodes[0].toString() | ||
// One of the items is a pseudo and the other one isn't. Let's move | ||
// the pseudo to the right. | ||
if ((a.type === 'pseudo') ^ (z.type === 'pseudo')) { | ||
return (a.type === 'pseudo') - (z.type === 'pseudo') | ||
} | ||
// Does `selector` contain a :merge() pseudo-class with the same value? | ||
let existing = merges.find((merge) => merge.value === value) | ||
// Both are pseudo's, move the pseudo elements (except for | ||
// ::file-selector-button) to the right. | ||
return isPseudoElement(a) - isPseudoElement(z) | ||
} | ||
// Nope so there's nothing to do | ||
if (!existing) { | ||
return | ||
} | ||
function isPseudoElement(node) { | ||
if (node.type !== 'pseudo') return false | ||
if (pseudoElementExceptions.includes(node.value)) return false | ||
// Everything after `:merge()` up to the next combinator is what is attached to the merged selector | ||
let attachments = [] | ||
let next = pseudo.next() | ||
while (next && next.type !== 'combinator') { | ||
attachments.push(next) | ||
next = next.next() | ||
} | ||
return node.value.startsWith('::') || pseudoElementsBC.includes(node.value) | ||
} | ||
let combinator = next | ||
function resolveFunctionArgument(haystack, needle, arg) { | ||
let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle) | ||
if (startIdx === -1) return null | ||
existing.pseudo.parent.insertAfter( | ||
existing.pseudo, | ||
selectorParser.selector({ nodes: attachments.map((node) => node.clone()) }) | ||
) | ||
// Start inside the `(` | ||
startIdx += needle.length + 1 | ||
pseudo.remove() | ||
attachments.forEach((node) => node.remove()) | ||
let target = '' | ||
let count = 0 | ||
for (let char of haystack.slice(startIdx)) { | ||
if (char !== '(' && char !== ')') { | ||
target += char | ||
} else if (char === '(') { | ||
target += char | ||
count++ | ||
} else if (char === ')') { | ||
if (--count < 0) break // unbalanced | ||
target += char | ||
// What about this case: | ||
// :merge(.group):focus > & | ||
// :merge(.group):hover & | ||
if (combinator && combinator.type === 'combinator') { | ||
combinator.remove() | ||
} | ||
} | ||
}) | ||
return target | ||
return [selector, format] | ||
} |
@@ -1,6 +0,6 @@ | ||
import defaultConfig from '../../stubs/defaultConfig.stub.js' | ||
import defaultFullConfig from '../../stubs/config.full.js' | ||
import { flagEnabled } from '../featureFlags' | ||
export default function getAllConfigs(config) { | ||
const configs = (config?.presets ?? [defaultConfig]) | ||
const configs = (config?.presets ?? [defaultFullConfig]) | ||
.slice() | ||
@@ -12,2 +12,21 @@ .reverse() | ||
// Add experimental configs here... | ||
respectDefaultRingColorOpacity: { | ||
theme: { | ||
ringColor: ({ theme }) => ({ | ||
DEFAULT: '#3b82f67f', | ||
...theme('colors'), | ||
}), | ||
}, | ||
}, | ||
disableColorOpacityUtilitiesByDefault: { | ||
corePlugins: { | ||
backgroundOpacity: false, | ||
borderOpacity: false, | ||
divideOpacity: false, | ||
placeholderOpacity: false, | ||
ringOpacity: false, | ||
textOpacity: false, | ||
}, | ||
}, | ||
} | ||
@@ -14,0 +33,0 @@ |
@@ -7,3 +7,3 @@ export default function isPlainObject(value) { | ||
const prototype = Object.getPrototypeOf(value) | ||
return prototype === null || prototype === Object.prototype | ||
return prototype === null || Object.getPrototypeOf(prototype) === null | ||
} |
@@ -6,3 +6,3 @@ import colors from 'picocolors' | ||
function log(type, messages, key) { | ||
if (process.env.JEST_WORKER_ID !== undefined) return | ||
if (typeof process !== 'undefined' && process.env.JEST_WORKER_ID) return | ||
@@ -9,0 +9,0 @@ if (key && alreadyShown.has(key)) return |
@@ -25,3 +25,7 @@ import escapeClassName from './escapeClassName' | ||
if (key.startsWith('/')) { | ||
return `${classPrefix}${key}` | ||
} | ||
return `${classPrefix}-${key}` | ||
} |
@@ -1,2 +0,2 @@ | ||
export default function (value) { | ||
export default function negateValue(value) { | ||
value = `${value}` | ||
@@ -13,5 +13,13 @@ | ||
if (value.includes('var(') || value.includes('calc(')) { | ||
return `calc(${value} * -1)` | ||
// What functions we support negating numeric values for | ||
// var() isn't inherently a numeric function but we support it anyway | ||
// The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_ | ||
// to produce generally useful results and that will be covered already | ||
let numericFunctions = ['var', 'calc', 'min', 'max', 'clamp'] | ||
for (const fn of numericFunctions) { | ||
if (value.includes(`${fn}(`)) { | ||
return `calc(${value} * -1)` | ||
} | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
import { flagEnabled } from '../featureFlags' | ||
import log, { dim } from './log' | ||
@@ -59,5 +60,7 @@ | ||
if (typeof config.content === 'object' && config.content !== null) { | ||
// Only `files`, `extract` and `transform` can exist in `config.content` | ||
// Only `files`, `relative`, `extract`, and `transform` can exist in `config.content` | ||
if ( | ||
Object.keys(config.content).some((key) => !['files', 'extract', 'transform'].includes(key)) | ||
Object.keys(config.content).some( | ||
(key) => !['files', 'relative', 'extract', 'transform'].includes(key) | ||
) | ||
) { | ||
@@ -116,2 +119,10 @@ return false | ||
} | ||
// `config.content.relative` is optional and can be a boolean | ||
if ( | ||
typeof config.content.relative !== 'boolean' && | ||
typeof config.content.relative !== 'undefined' | ||
) { | ||
return false | ||
} | ||
} | ||
@@ -145,2 +156,20 @@ | ||
// Normalize the `blocklist` | ||
config.blocklist = (() => { | ||
let { blocklist } = config | ||
if (Array.isArray(blocklist)) { | ||
if (blocklist.every((item) => typeof item === 'string')) { | ||
return blocklist | ||
} | ||
log.warn('blocklist-invalid', [ | ||
'The `blocklist` option must be an array of strings.', | ||
'https://tailwindcss.com/docs/content-configuration#discarding-classes', | ||
]) | ||
} | ||
return [] | ||
})() | ||
// Normalize prefix option | ||
@@ -160,2 +189,12 @@ if (typeof config.prefix === 'function') { | ||
config.content = { | ||
relative: (() => { | ||
let { content } = config | ||
if (content?.relative) { | ||
return content.relative | ||
} | ||
return flagEnabled(config, 'relativeContentPathsByDefault') | ||
})(), | ||
files: (() => { | ||
@@ -242,5 +281,3 @@ let { content, purge } = config | ||
transformers.DEFAULT = transform | ||
} | ||
if (typeof transform === 'object' && transform !== null) { | ||
} else if (typeof transform === 'object' && transform !== null) { | ||
Object.assign(transformers, transform) | ||
@@ -247,0 +284,0 @@ } |
/** | ||
* @typedef {object} ScreenValue | ||
* @property {number|undefined} min | ||
* @property {number|undefined} max | ||
* @property {string|undefined} raw | ||
*/ | ||
/** | ||
* @typedef {object} Screen | ||
* @property {string} name | ||
* @property {boolean} not | ||
* @property {ScreenValue[]} values | ||
*/ | ||
/** | ||
* A function that normalizes the various forms that the screens object can be | ||
@@ -13,2 +27,4 @@ * provided in. | ||
* - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values | ||
* | ||
* @returns {Screen[]} | ||
*/ | ||
@@ -23,3 +39,3 @@ export function normalizeScreens(screens, root = true) { | ||
if (typeof screen === 'string') { | ||
return { name: screen.toString(), values: [{ min: screen, max: undefined }] } | ||
return { name: screen.toString(), not: false, values: [{ min: screen, max: undefined }] } | ||
} | ||
@@ -31,10 +47,10 @@ | ||
if (typeof options === 'string') { | ||
return { name, values: [{ min: options, max: undefined }] } | ||
return { name, not: false, values: [{ min: options, max: undefined }] } | ||
} | ||
if (Array.isArray(options)) { | ||
return { name, values: options.map((option) => resolveValue(option)) } | ||
return { name, not: false, values: options.map((option) => resolveValue(option)) } | ||
} | ||
return { name, values: [resolveValue(options)] } | ||
return { name, not: false, values: [resolveValue(options)] } | ||
}) | ||
@@ -46,4 +62,83 @@ } | ||
/** | ||
* @param {Screen} screen | ||
* @returns {{result: false, reason: string} | {result: true, reason: null}} | ||
*/ | ||
export function isScreenSortable(screen) { | ||
if (screen.values.length !== 1) { | ||
return { result: false, reason: 'multiple-values' } | ||
} else if (screen.values[0].raw !== undefined) { | ||
return { result: false, reason: 'raw-values' } | ||
} else if (screen.values[0].min !== undefined && screen.values[0].max !== undefined) { | ||
return { result: false, reason: 'min-and-max' } | ||
} | ||
return { result: true, reason: null } | ||
} | ||
/** | ||
* @param {'min' | 'max'} type | ||
* @param {Screen | 'string'} a | ||
* @param {Screen | 'string'} z | ||
* @returns {number} | ||
*/ | ||
export function compareScreens(type, a, z) { | ||
let aScreen = toScreen(a, type) | ||
let zScreen = toScreen(z, type) | ||
let aSorting = isScreenSortable(aScreen) | ||
let bSorting = isScreenSortable(zScreen) | ||
// These cases should never happen and indicate a bug in Tailwind CSS itself | ||
if (aSorting.reason === 'multiple-values' || bSorting.reason === 'multiple-values') { | ||
throw new Error( | ||
'Attempted to sort a screen with multiple values. This should never happen. Please open a bug report.' | ||
) | ||
} else if (aSorting.reason === 'raw-values' || bSorting.reason === 'raw-values') { | ||
throw new Error( | ||
'Attempted to sort a screen with raw values. This should never happen. Please open a bug report.' | ||
) | ||
} else if (aSorting.reason === 'min-and-max' || bSorting.reason === 'min-and-max') { | ||
throw new Error( | ||
'Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report.' | ||
) | ||
} | ||
// Let the sorting begin | ||
let { min: aMin, max: aMax } = aScreen.values[0] | ||
let { min: zMin, max: zMax } = zScreen.values[0] | ||
// Negating screens flip their behavior. Basically `not min-width` is `max-width` | ||
if (a.not) [aMin, aMax] = [aMax, aMin] | ||
if (z.not) [zMin, zMax] = [zMax, zMin] | ||
aMin = aMin === undefined ? aMin : parseFloat(aMin) | ||
aMax = aMax === undefined ? aMax : parseFloat(aMax) | ||
zMin = zMin === undefined ? zMin : parseFloat(zMin) | ||
zMax = zMax === undefined ? zMax : parseFloat(zMax) | ||
let [aValue, zValue] = type === 'min' ? [aMin, zMin] : [zMax, aMax] | ||
return aValue - zValue | ||
} | ||
/** | ||
* | ||
* @param {PartialScreen> | string} value | ||
* @param {'min' | 'max'} type | ||
* @returns {Screen} | ||
*/ | ||
export function toScreen(value, type) { | ||
if (typeof value === 'object') { | ||
return value | ||
} | ||
return { | ||
name: 'arbitrary-screen', | ||
values: [{ [type]: value }], | ||
} | ||
} | ||
function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) { | ||
return { min, max, raw } | ||
} |
@@ -16,3 +16,3 @@ const DIRECTIONS = new Set(['normal', 'reverse', 'alternate', 'alternate-reverse']) | ||
const COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. | ||
const COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubic-bezier(a, b, c)` these don't count. | ||
const SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead. | ||
@@ -19,0 +19,0 @@ const TIME = /^(-?[\d.]+m?s)$/ |
@@ -8,3 +8,3 @@ import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' | ||
export function parseBoxShadowValue(input) { | ||
let shadows = Array.from(splitAtTopLevelOnly(input, ',')) | ||
let shadows = splitAtTopLevelOnly(input, ',') | ||
return shadows.map((shadow) => { | ||
@@ -11,0 +11,0 @@ let value = shadow.trim() |
@@ -1,49 +0,44 @@ | ||
import isGlob from 'is-glob' | ||
import globParent from 'glob-parent' | ||
import path from 'path' | ||
// @ts-check | ||
// Based on `glob-base` | ||
// https://github.com/micromatch/glob-base/blob/master/index.js | ||
function parseGlob(pattern) { | ||
let glob = pattern | ||
let base = globParent(pattern) | ||
/** | ||
* @typedef {{type: 'dependency', file: string} | {type: 'dir-dependency', dir: string, glob: string}} Dependency | ||
*/ | ||
if (base !== '.') { | ||
glob = pattern.substr(base.length) | ||
if (glob.charAt(0) === '/') { | ||
glob = glob.substr(1) | ||
} | ||
/** | ||
* | ||
* @param {import('../lib/content.js').ContentPath} contentPath | ||
* @returns {Dependency[]} | ||
*/ | ||
export default function parseDependency(contentPath) { | ||
if (contentPath.ignore) { | ||
return [] | ||
} | ||
if (glob.substr(0, 2) === './') { | ||
glob = glob.substr(2) | ||
if (!contentPath.glob) { | ||
return [ | ||
{ | ||
type: 'dependency', | ||
file: contentPath.base, | ||
}, | ||
] | ||
} | ||
if (glob.charAt(0) === '/') { | ||
glob = glob.substr(1) | ||
} | ||
return { base, glob } | ||
} | ||
export default function parseDependency(normalizedFileOrGlob) { | ||
if (normalizedFileOrGlob.startsWith('!')) { | ||
return null | ||
if (process.env.ROLLUP_WATCH === 'true') { | ||
// rollup-plugin-postcss does not support dir-dependency messages | ||
// but directories can be watched in the same way as files | ||
return [ | ||
{ | ||
type: 'dependency', | ||
file: contentPath.base, | ||
}, | ||
] | ||
} | ||
let message | ||
if (isGlob(normalizedFileOrGlob)) { | ||
let { base, glob } = parseGlob(normalizedFileOrGlob) | ||
message = { type: 'dir-dependency', dir: path.resolve(base), glob } | ||
} else { | ||
message = { type: 'dependency', file: path.resolve(normalizedFileOrGlob) } | ||
} | ||
// rollup-plugin-postcss does not support dir-dependency messages | ||
// but directories can be watched in the same way as files | ||
if (message.type === 'dir-dependency' && process.env.ROLLUP_WATCH === 'true') { | ||
message = { type: 'dependency', file: message.dir } | ||
} | ||
return message | ||
return [ | ||
{ | ||
type: 'dir-dependency', | ||
dir: contentPath.base, | ||
glob: contentPath.glob, | ||
}, | ||
] | ||
} |
@@ -1,2 +0,1 @@ | ||
import selectorParser from 'postcss-selector-parser' | ||
import escapeCommas from './escapeCommas' | ||
@@ -21,17 +20,18 @@ import { withAlphaValue } from './withAlphaVariable' | ||
import negateValue from './negateValue' | ||
import { backgroundSize } from './validateFormalSyntax' | ||
import { flagEnabled } from '../featureFlags.js' | ||
/** | ||
* @param {import('postcss-selector-parser').Container} selectors | ||
* @param {(className: string) => string} updateClass | ||
* @returns {string} | ||
*/ | ||
export function updateAllClasses(selectors, updateClass) { | ||
let parser = selectorParser((selectors) => { | ||
selectors.walkClasses((sel) => { | ||
let updatedClass = updateClass(sel.value) | ||
sel.value = updatedClass | ||
if (sel.raws && sel.raws.value) { | ||
sel.raws.value = escapeCommas(sel.raws.value) | ||
} | ||
}) | ||
selectors.walkClasses((sel) => { | ||
sel.value = updateClass(sel.value) | ||
if (sel.raws && sel.raws.value) { | ||
sel.raws.value = escapeCommas(sel.raws.value) | ||
} | ||
}) | ||
let result = parser.processSync(selectors) | ||
return result | ||
} | ||
@@ -89,18 +89,59 @@ | ||
function splitAlpha(modifier) { | ||
function splitUtilityModifier(modifier) { | ||
let slashIdx = modifier.lastIndexOf('/') | ||
// If the `/` is inside an arbitrary, we want to find the previous one if any | ||
// This logic probably isn't perfect but it should work for most cases | ||
let arbitraryStartIdx = modifier.lastIndexOf('[', slashIdx) | ||
let arbitraryEndIdx = modifier.indexOf(']', slashIdx) | ||
let isNextToArbitrary = modifier[slashIdx - 1] === ']' || modifier[slashIdx + 1] === '[' | ||
// Backtrack to the previous `/` if the one we found was inside an arbitrary | ||
if (!isNextToArbitrary) { | ||
if (arbitraryStartIdx !== -1 && arbitraryEndIdx !== -1) { | ||
if (arbitraryStartIdx < slashIdx && slashIdx < arbitraryEndIdx) { | ||
slashIdx = modifier.lastIndexOf('/', arbitraryStartIdx) | ||
} | ||
} | ||
} | ||
if (slashIdx === -1 || slashIdx === modifier.length - 1) { | ||
return [modifier] | ||
return [modifier, undefined] | ||
} | ||
let arbitrary = isArbitraryValue(modifier) | ||
// The modifier could be of the form `[foo]/[bar]` | ||
// We want to handle this case properly | ||
// without affecting `[foo/bar]` | ||
if (arbitrary && !modifier.includes(']/[')) { | ||
return [modifier, undefined] | ||
} | ||
return [modifier.slice(0, slashIdx), modifier.slice(slashIdx + 1)] | ||
} | ||
export function parseColorFormat(value) { | ||
if (typeof value === 'string' && value.includes('<alpha-value>')) { | ||
let oldValue = value | ||
return ({ opacityValue = 1 }) => oldValue.replace(/<alpha-value>/g, opacityValue) | ||
} | ||
return value | ||
} | ||
function unwrapArbitraryModifier(modifier) { | ||
return normalize(modifier.slice(1, -1)) | ||
} | ||
export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) { | ||
if (options.values?.[modifier] !== undefined) { | ||
return options.values?.[modifier] | ||
return parseColorFormat(options.values?.[modifier]) | ||
} | ||
let [color, alpha] = splitAlpha(modifier) | ||
// TODO: Hoist this up to getMatchingTypes or something | ||
// We do this here because we need the alpha value (if any) | ||
let [color, alpha] = splitUtilityModifier(modifier) | ||
@@ -115,4 +156,6 @@ if (alpha !== undefined) { | ||
normalizedColor = parseColorFormat(normalizedColor) | ||
if (isArbitraryValue(alpha)) { | ||
return withAlphaValue(normalizedColor, alpha.slice(1, -1)) | ||
return withAlphaValue(normalizedColor, unwrapArbitraryModifier(alpha)) | ||
} | ||
@@ -140,3 +183,3 @@ | ||
let typeMap = { | ||
export let typeMap = { | ||
any: asValue, | ||
@@ -157,2 +200,3 @@ color: asColor, | ||
shadow: guess(shadow), | ||
size: guess(backgroundSize), | ||
} | ||
@@ -169,2 +213,16 @@ | ||
export function coerceValue(types, modifier, options, tailwindConfig) { | ||
if (options.values && modifier in options.values) { | ||
for (let { type } of types ?? []) { | ||
let result = typeMap[type](modifier, options, { | ||
tailwindConfig, | ||
}) | ||
if (result === undefined) { | ||
continue | ||
} | ||
return [result, type, null] | ||
} | ||
} | ||
if (isArbitraryValue(modifier)) { | ||
@@ -187,10 +245,11 @@ let arbitraryValue = modifier.slice(1, -1) | ||
if (value.length > 0 && supportedTypes.includes(explicitType)) { | ||
return [asValue(`[${value}]`, options), explicitType] | ||
return [asValue(`[${value}]`, options), explicitType, null] | ||
} | ||
} | ||
let matches = getMatchingTypes(types, modifier, options, tailwindConfig) | ||
// Find first matching type | ||
for (let type of [].concat(types)) { | ||
let result = typeMap[type](modifier, options, { tailwindConfig }) | ||
if (result !== undefined) return [result, type] | ||
for (let match of matches) { | ||
return match | ||
} | ||
@@ -200,1 +259,57 @@ | ||
} | ||
/** | ||
* | ||
* @param {{type: string}[]} types | ||
* @param {string} rawModifier | ||
* @param {any} options | ||
* @param {any} tailwindConfig | ||
* @returns {Iterator<[value: string, type: string, modifier: string | null]>} | ||
*/ | ||
export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) { | ||
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers') | ||
let [modifier, utilityModifier] = splitUtilityModifier(rawModifier) | ||
let canUseUtilityModifier = | ||
modifiersEnabled && | ||
options.modifiers != null && | ||
(options.modifiers === 'any' || | ||
(typeof options.modifiers === 'object' && | ||
((utilityModifier && isArbitraryValue(utilityModifier)) || | ||
utilityModifier in options.modifiers))) | ||
if (!canUseUtilityModifier) { | ||
modifier = rawModifier | ||
utilityModifier = undefined | ||
} | ||
if (utilityModifier !== undefined && modifier === '') { | ||
modifier = 'DEFAULT' | ||
} | ||
// Check the full value first | ||
// TODO: Move to asValue… somehow | ||
if (utilityModifier !== undefined) { | ||
if (typeof options.modifiers === 'object') { | ||
let configValue = options.modifiers?.[utilityModifier] ?? null | ||
if (configValue !== null) { | ||
utilityModifier = configValue | ||
} else if (isArbitraryValue(utilityModifier)) { | ||
utilityModifier = unwrapArbitraryModifier(utilityModifier) | ||
} | ||
} | ||
} | ||
for (let { type } of types ?? []) { | ||
let result = typeMap[type](modifier, options, { | ||
tailwindConfig, | ||
}) | ||
if (result === undefined) { | ||
continue | ||
} | ||
yield [result, type, utilityModifier ?? null] | ||
} | ||
} |
import parser from 'postcss-selector-parser' | ||
/** | ||
* @template {string | import('postcss-selector-parser').Root} T | ||
* | ||
* Prefix all classes in the selector with the given prefix | ||
* | ||
* It can take either a string or a selector AST and will return the same type | ||
* | ||
* @param {string} prefix | ||
* @param {T} selector | ||
* @param {boolean} prependNegative | ||
* @returns {T} | ||
*/ | ||
export default function (prefix, selector, prependNegative = false) { | ||
return parser((selectors) => { | ||
selectors.walkClasses((classSelector) => { | ||
let baseClass = classSelector.value | ||
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-') | ||
if (prefix === '') { | ||
return selector | ||
} | ||
classSelector.value = shouldPlaceNegativeBeforePrefix | ||
? `-${prefix}${baseClass.slice(1)}` | ||
: `${prefix}${baseClass}` | ||
}) | ||
}).processSync(selector) | ||
/** @type {import('postcss-selector-parser').Root} */ | ||
let ast = typeof selector === 'string' ? parser().astSync(selector) : selector | ||
ast.walkClasses((classSelector) => { | ||
let baseClass = classSelector.value | ||
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-') | ||
classSelector.value = shouldPlaceNegativeBeforePrefix | ||
? `-${prefix}${baseClass.slice(1)}` | ||
: `${prefix}${baseClass}` | ||
}) | ||
return typeof selector === 'string' ? ast.toString() : ast | ||
} |
import negateValue from './negateValue' | ||
import corePluginList from '../corePluginList' | ||
import configurePlugins from './configurePlugins' | ||
import defaultConfig from '../../stubs/defaultConfig.stub' | ||
import colors from '../public/colors' | ||
@@ -11,2 +10,5 @@ import { defaults } from './defaults' | ||
import { cloneDeep } from './cloneDeep' | ||
import { parseColorFormat } from './pluginUtils' | ||
import { withAlphaValue } from './withAlphaVariable' | ||
import toColorValue from './toColorValue' | ||
@@ -17,6 +19,2 @@ function isFunction(input) { | ||
function isObject(input) { | ||
return typeof input === 'object' && input !== null | ||
} | ||
function mergeWith(target, ...sources) { | ||
@@ -30,4 +28,4 @@ let customizer = sources.pop() | ||
if (merged === undefined) { | ||
if (isObject(target[k]) && isObject(source[k])) { | ||
target[k] = mergeWith(target[k], source[k], customizer) | ||
if (isPlainObject(target[k]) && isPlainObject(source[k])) { | ||
target[k] = mergeWith({}, target[k], source[k], customizer) | ||
} else { | ||
@@ -72,32 +70,2 @@ target[k] = source[k] | ||
}, | ||
rgb(property) { | ||
if (!property.startsWith('--')) { | ||
throw new Error( | ||
'The rgb() helper requires a custom property name to be passed as the first argument.' | ||
) | ||
} | ||
return ({ opacityValue }) => { | ||
if (opacityValue === undefined || opacityValue === 1) { | ||
return `rgb(var(${property}) / 1.0)` | ||
} | ||
return `rgb(var(${property}) / ${opacityValue})` | ||
} | ||
}, | ||
hsl(property) { | ||
if (!property.startsWith('--')) { | ||
throw new Error( | ||
'The hsl() helper requires a custom property name to be passed as the first argument.' | ||
) | ||
} | ||
return ({ opacityValue }) => { | ||
if (opacityValue === undefined || opacityValue === 1) { | ||
return `hsl(var(${property}) / 1)` | ||
} | ||
return `hsl(var(${property}) / ${opacityValue})` | ||
} | ||
}, | ||
} | ||
@@ -137,3 +105,3 @@ | ||
// When we have an array of objects, we do want to merge it | ||
if (Array.isArray(merged) && isObject(merged[0])) { | ||
if (Array.isArray(merged) && isPlainObject(merged[0])) { | ||
return merged.concat(value) | ||
@@ -143,3 +111,3 @@ } | ||
// When the incoming value is an array, and the existing config is an object, prepend the existing object | ||
if (Array.isArray(value) && isObject(value[0]) && isObject(merged)) { | ||
if (Array.isArray(value) && isPlainObject(value[0]) && isPlainObject(merged)) { | ||
return [merged, ...value] | ||
@@ -173,36 +141,77 @@ } | ||
/** | ||
* | ||
* @param {string} key | ||
* @return {Iterable<string[] & {alpha: string | undefined}>} | ||
*/ | ||
function* toPaths(key) { | ||
let path = toPath(key) | ||
if (path.length === 0) { | ||
return | ||
} | ||
yield path | ||
if (Array.isArray(key)) { | ||
return | ||
} | ||
let pattern = /^(.*?)\s*\/\s*([^/]+)$/ | ||
let matches = key.match(pattern) | ||
if (matches !== null) { | ||
let [, prefix, alpha] = matches | ||
let newPath = toPath(prefix) | ||
newPath.alpha = alpha | ||
yield newPath | ||
} | ||
} | ||
function resolveFunctionKeys(object) { | ||
// theme('colors.red.500 / 0.5') -> ['colors', 'red', '500 / 0', '5] | ||
const resolvePath = (key, defaultValue) => { | ||
const path = toPath(key) | ||
for (const path of toPaths(key)) { | ||
let index = 0 | ||
let val = object | ||
let index = 0 | ||
let val = object | ||
while (val !== undefined && val !== null && index < path.length) { | ||
val = val[path[index++]] | ||
while (val !== undefined && val !== null && index < path.length) { | ||
val = val[path[index++]] | ||
val = isFunction(val) ? val(resolvePath, configUtils) : val | ||
} | ||
let shouldResolveAsFn = | ||
isFunction(val) && (path.alpha === undefined || index <= path.length - 1) | ||
if (val === undefined) { | ||
return defaultValue | ||
} | ||
val = shouldResolveAsFn ? val(resolvePath, configUtils) : val | ||
} | ||
if (isPlainObject(val)) { | ||
return cloneDeep(val) | ||
if (val !== undefined) { | ||
if (path.alpha !== undefined) { | ||
let normalized = parseColorFormat(val) | ||
return withAlphaValue(normalized, path.alpha, toColorValue(normalized)) | ||
} | ||
if (isPlainObject(val)) { | ||
return cloneDeep(val) | ||
} | ||
return val | ||
} | ||
} | ||
return val | ||
return defaultValue | ||
} | ||
resolvePath.theme = resolvePath | ||
Object.assign(resolvePath, { | ||
theme: resolvePath, | ||
...configUtils, | ||
}) | ||
for (let key in configUtils) { | ||
resolvePath[key] = configUtils[key] | ||
} | ||
return Object.keys(object).reduce((resolved, key) => { | ||
resolved[key] = isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key] | ||
return Object.keys(object).reduce((resolved, key) => { | ||
return { | ||
...resolved, | ||
[key]: isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key], | ||
} | ||
return resolved | ||
}, {}) | ||
@@ -260,3 +269,2 @@ } | ||
separator: ':', | ||
variantOrder: defaultConfig.variantOrder, | ||
}, | ||
@@ -263,0 +271,0 @@ ] |
import fs from 'fs' | ||
import path from 'path' | ||
const defaultConfigFiles = [ | ||
'./tailwind.config.js', | ||
'./tailwind.config.cjs', | ||
'./tailwind.config.mjs', | ||
'./tailwind.config.ts', | ||
'./tailwind.config.cts', | ||
'./tailwind.config.mts', | ||
] | ||
function isObject(value) { | ||
@@ -46,3 +55,7 @@ return typeof value === 'object' && value !== null | ||
// require('tailwindcss') | ||
for (const configFile of ['./tailwind.config.js', './tailwind.config.cjs']) { | ||
return resolveDefaultConfigPath() | ||
} | ||
export function resolveDefaultConfigPath() { | ||
for (const configFile of defaultConfigFiles) { | ||
try { | ||
@@ -49,0 +62,0 @@ const configPath = path.resolve(configFile) |
@@ -1,3 +0,1 @@ | ||
import * as regex from '../lib/regex' | ||
/** | ||
@@ -18,55 +16,34 @@ * This splits a string on a top-level character. | ||
*/ | ||
export function* splitAtTopLevelOnly(input, separator) { | ||
let SPECIALS = new RegExp(`[(){}\\[\\]${regex.escape(separator)}]`, 'g') | ||
export function splitAtTopLevelOnly(input, separator) { | ||
let stack = [] | ||
let parts = [] | ||
let lastPos = 0 | ||
let isEscaped = false | ||
let depth = 0 | ||
let lastIndex = 0 | ||
let found = false | ||
let separatorIndex = 0 | ||
let separatorStart = 0 | ||
let separatorLength = separator.length | ||
for (let idx = 0; idx < input.length; idx++) { | ||
let char = input[idx] | ||
// Find all paren-like things & character | ||
// And only split on commas if they're top-level | ||
for (let match of input.matchAll(SPECIALS)) { | ||
let matchesSeparator = match[0] === separator[separatorIndex] | ||
let atEndOfSeparator = separatorIndex === separatorLength - 1 | ||
let matchesFullSeparator = matchesSeparator && atEndOfSeparator | ||
if (match[0] === '(') depth++ | ||
if (match[0] === ')') depth-- | ||
if (match[0] === '[') depth++ | ||
if (match[0] === ']') depth-- | ||
if (match[0] === '{') depth++ | ||
if (match[0] === '}') depth-- | ||
if (matchesSeparator && depth === 0) { | ||
if (separatorStart === 0) { | ||
separatorStart = match.index | ||
if (stack.length === 0 && char === separator[0] && !isEscaped) { | ||
if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) { | ||
parts.push(input.slice(lastPos, idx)) | ||
lastPos = idx + separator.length | ||
} | ||
separatorIndex++ | ||
} | ||
if (matchesFullSeparator && depth === 0) { | ||
found = true | ||
isEscaped = isEscaped ? false : char === '\\' | ||
yield input.substring(lastIndex, separatorStart) | ||
lastIndex = separatorStart + separatorLength | ||
if (char === '(' || char === '[' || char === '{') { | ||
stack.push(char) | ||
} else if ( | ||
(char === ')' && stack[stack.length - 1] === '(') || | ||
(char === ']' && stack[stack.length - 1] === '[') || | ||
(char === '}' && stack[stack.length - 1] === '{') | ||
) { | ||
stack.pop() | ||
} | ||
if (separatorIndex === separatorLength) { | ||
separatorIndex = 0 | ||
separatorStart = 0 | ||
} | ||
} | ||
// Provide the last segment of the string if available | ||
// Otherwise the whole string since no `char`s were found | ||
// This mirrors the behavior of string.split() | ||
if (found) { | ||
yield input.substring(lastIndex) | ||
} else { | ||
yield input | ||
} | ||
parts.push(input.slice(lastPos)) | ||
return parts | ||
} |
@@ -7,3 +7,3 @@ /** | ||
* Example: | ||
* a -> ['a] | ||
* a -> ['a'] | ||
* a.b.c -> ['a', 'b', 'c'] | ||
@@ -10,0 +10,0 @@ * a[b].c -> ['a', 'b', 'c'] |
import postcss from 'postcss' | ||
import isPlainObject from './isPlainObject' | ||
@@ -13,5 +14,12 @@ export default function transformThemeValue(themeSection) { | ||
if (themeSection === 'fontFamily') { | ||
return (value) => { | ||
if (typeof value === 'function') value = value({}) | ||
let families = Array.isArray(value) && isPlainObject(value[1]) ? value[0] : value | ||
return Array.isArray(families) ? families.join(', ') : families | ||
} | ||
} | ||
if ( | ||
[ | ||
'fontFamily', | ||
'boxShadow', | ||
@@ -48,4 +56,6 @@ 'transitionProperty', | ||
return (value) => { | ||
if (typeof value === 'function') value = value({}) | ||
return (value, opts = {}) => { | ||
if (typeof value === 'function') { | ||
value = value(opts) | ||
} | ||
@@ -52,0 +62,0 @@ return value |
@@ -12,3 +12,16 @@ import log from './log' | ||
// Warn if the line-clamp plugin is installed | ||
try { | ||
let plugin = require('@tailwindcss/line-clamp') | ||
if (config.plugins.includes(plugin)) { | ||
log.warn('line-clamp-in-core', [ | ||
'As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.', | ||
'Remove it from the `plugins` array in your configuration to eliminate this warning.', | ||
]) | ||
config.plugins = config.plugins.filter((p) => p !== plugin) | ||
} | ||
} catch {} | ||
return config | ||
} |
@@ -8,3 +8,3 @@ import { parseColor, formatColor } from './color' | ||
let parsed = parseColor(color) | ||
let parsed = parseColor(color, { loose: true }) | ||
@@ -11,0 +11,0 @@ if (parsed === null) { |
@@ -1,2 +0,2 @@ | ||
import type { CorePluginList } from './generated/CorePluginList' | ||
import type { CorePluginList } from './generated/corePluginList' | ||
import type { DefaultColors } from './generated/colors' | ||
@@ -14,3 +14,4 @@ | ||
} | ||
type ResolvableTo<T> = T | ((utils: PluginUtils) => T) | ||
export type ResolvableTo<T> = T | ((utils: PluginUtils) => T) | ||
type CSSRuleObject = RecursiveKeyValuePair<string, null | string | string[]> | ||
@@ -34,2 +35,3 @@ interface PluginUtils { | ||
files: (FilePath | RawFile)[] | ||
relative?: boolean | ||
extract?: ExtractorFn | { [extension: string]: ExtractorFn } | ||
@@ -49,18 +51,20 @@ transform?: TransformerFn | { [extension: string]: TransformerFn } | ||
// Safelist related config | ||
type SafelistConfig = | ||
| string[] | ||
| { | ||
pattern: RegExp | ||
variants: string[] | ||
}[] | ||
type SafelistConfig = string | { pattern: RegExp; variants?: string[] } | ||
// Blocklist related config | ||
type BlocklistConfig = string | ||
// Presets related config | ||
type PresetsConfig = Config[] | ||
type PresetsConfig = Partial<Config> | ||
// Future related config | ||
type FutureConfigValues = never // Replace with 'future-feature-1' | 'future-feature-2' | ||
type FutureConfigValues = | ||
| 'hoverOnlyWhenSupported' | ||
| 'respectDefaultRingColorOpacity' | ||
| 'disableColorOpacityUtilitiesByDefault' | ||
| 'relativeContentPathsByDefault' | ||
type FutureConfig = Expand<'all' | Partial<Record<FutureConfigValues, boolean>>> | [] | ||
// Experimental related config | ||
type ExperimentalConfigValues = 'optimizeUniversalDefaults' // Replace with 'experimental-feature-1' | 'experimental-feature-2' | ||
type ExperimentalConfigValues = 'optimizeUniversalDefaults' | 'matchVariant' | ||
type ExperimentalConfig = Expand<'all' | Partial<Record<ExperimentalConfigValues, boolean>>> | [] | ||
@@ -70,8 +74,15 @@ | ||
type DarkModeConfig = | ||
/** Use the `media` query strategy. */ | ||
// Use the `media` query strategy. | ||
| 'media' | ||
/** Use the `class` stategy, which requires a `.dark` class on the `html`. */ | ||
// Use the `class` strategy, which requires a `.dark` class on the `html`. | ||
| 'class' | ||
/** Use the `class` stategy with a custom class instead of `.dark`. */ | ||
// Use the `class` strategy with a custom class instead of `.dark`. | ||
| ['class', string] | ||
// Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior | ||
| 'selector' | ||
// Use the `selector` strategy with a custom selector instead of `.dark`. | ||
| ['selector', string] | ||
// Use the `variant` strategy, which allows you to completely customize the selector | ||
// It takes a string or an array of strings, which are passed directly to `addVariant()` | ||
| ['variant', string | string[]] | ||
@@ -82,13 +93,13 @@ type Screen = { raw: string } | { min: string } | { max: string } | { min: string; max: string } | ||
// Theme related config | ||
interface ThemeConfig { | ||
extend: Partial<Omit<ThemeConfig, 'extend'>> | ||
/** Responsiveness */ | ||
export interface ThemeConfig { | ||
// Responsiveness | ||
screens: ResolvableTo<ScreensConfig> | ||
supports: ResolvableTo<Record<string, string>> | ||
data: ResolvableTo<Record<string, string>> | ||
/** Reusable base configs */ | ||
// Reusable base configs | ||
colors: ResolvableTo<RecursiveKeyValuePair> | ||
spacing: ResolvableTo<KeyValuePair> | ||
/** Components */ | ||
// Components | ||
container: ResolvableTo< | ||
@@ -102,3 +113,3 @@ Partial<{ | ||
/** Utilities */ | ||
// Utilities | ||
inset: ThemeConfig['spacing'] | ||
@@ -163,3 +174,16 @@ zIndex: ResolvableTo<KeyValuePair> | ||
textIndent: ThemeConfig['spacing'] | ||
fontFamily: ResolvableTo<KeyValuePair<string, string[]>> | ||
fontFamily: ResolvableTo< | ||
KeyValuePair< | ||
string, | ||
| string | ||
| string[] | ||
| [ | ||
fontFamily: string | string[], | ||
configuration: Partial<{ | ||
fontFeatureSettings: string | ||
fontVariationSettings: string | ||
}> | ||
] | ||
> | ||
> | ||
fontSize: ResolvableTo< | ||
@@ -175,2 +199,3 @@ KeyValuePair< | ||
letterSpacing: string | ||
fontWeight: string | number | ||
}> | ||
@@ -206,3 +231,3 @@ ] | ||
contrast: ResolvableTo<KeyValuePair> | ||
dropShadow: ResolvableTo<KeyValuePair> | ||
dropShadow: ResolvableTo<KeyValuePair<string, string | string[]>> | ||
grayscale: ResolvableTo<KeyValuePair> | ||
@@ -228,4 +253,5 @@ hueRotate: ResolvableTo<KeyValuePair> | ||
content: ResolvableTo<KeyValuePair> | ||
} | ||
/** Custom */ | ||
interface CustomThemeConfig extends ThemeConfig { | ||
[key: string]: any | ||
@@ -255,5 +281,5 @@ } | ||
export interface PluginAPI { | ||
/** for registering new static utility styles */ | ||
// for registering new static utility styles | ||
addUtilities( | ||
utilities: RecursiveKeyValuePair | RecursiveKeyValuePair[], | ||
utilities: CSSRuleObject | CSSRuleObject[], | ||
options?: Partial<{ | ||
@@ -264,5 +290,8 @@ respectPrefix: boolean | ||
): void | ||
/** for registering new dynamic utility styles */ | ||
matchUtilities<T>( | ||
utilities: KeyValuePair<string, (value: T) => RecursiveKeyValuePair>, | ||
// for registering new dynamic utility styles | ||
matchUtilities<T = string, U = string>( | ||
utilities: KeyValuePair< | ||
string, | ||
(value: T | string, extra: { modifier: U | string | null }) => CSSRuleObject | null | ||
>, | ||
options?: Partial<{ | ||
@@ -273,8 +302,9 @@ respectPrefix: boolean | ||
values: KeyValuePair<string, T> | ||
modifiers: 'any' | KeyValuePair<string, U> | ||
supportsNegativeValues: boolean | ||
}> | ||
): void | ||
/** for registering new static component styles */ | ||
// for registering new static component styles | ||
addComponents( | ||
components: RecursiveKeyValuePair | RecursiveKeyValuePair[], | ||
components: CSSRuleObject | CSSRuleObject[], | ||
options?: Partial<{ | ||
@@ -285,5 +315,8 @@ respectPrefix: boolean | ||
): void | ||
/** for registering new dynamic component styles */ | ||
matchComponents<T>( | ||
components: KeyValuePair<string, (value: T) => RecursiveKeyValuePair>, | ||
// for registering new dynamic component styles | ||
matchComponents<T = string, U = string>( | ||
components: KeyValuePair< | ||
string, | ||
(value: T | string, extra: { modifier: U | string | null }) => CSSRuleObject | null | ||
>, | ||
options?: Partial<{ | ||
@@ -294,10 +327,22 @@ respectPrefix: boolean | ||
values: KeyValuePair<string, T> | ||
modifiers: 'any' | KeyValuePair<string, U> | ||
supportsNegativeValues: boolean | ||
}> | ||
): void | ||
/** for registering new base styles */ | ||
addBase(base: RecursiveKeyValuePair | RecursiveKeyValuePair[]): void | ||
/** for registering custom variants */ | ||
// for registering new base styles | ||
addBase(base: CSSRuleObject | CSSRuleObject[]): void | ||
// for registering custom variants | ||
addVariant(name: string, definition: string | string[] | (() => string) | (() => string)[]): void | ||
/** for looking up values in the user’s theme configuration */ | ||
matchVariant<T = string>( | ||
name: string, | ||
cb: (value: T | string, extra: { modifier: string | null }) => string | string[], | ||
options?: { | ||
values?: KeyValuePair<string, T> | ||
sort?( | ||
a: { value: T | string; modifier: string | null }, | ||
b: { value: T | string; modifier: string | null } | ||
): number | ||
} | ||
): void | ||
// for looking up values in the user’s theme configuration | ||
theme: <TDefaultValue = Config['theme']>( | ||
@@ -307,11 +352,18 @@ path?: string, | ||
) => TDefaultValue | ||
/** for looking up values in the user’s Tailwind configuration */ | ||
// for looking up values in the user’s Tailwind configuration | ||
config: <TDefaultValue = Config>(path?: string, defaultValue?: TDefaultValue) => TDefaultValue | ||
/** for checking if a core plugin is enabled */ | ||
// for checking if a core plugin is enabled | ||
corePlugins(path: string): boolean | ||
/** for manually escaping strings meant to be used in class names */ | ||
// for manually escaping strings meant to be used in class names | ||
e: (className: string) => string | ||
} | ||
export type PluginCreator = (api: PluginAPI) => void | ||
export type PluginsConfig = (PluginCreator | { handler: PluginCreator; config?: Config })[] | ||
export type PluginsConfig = ( | ||
| PluginCreator | ||
| { handler: PluginCreator; config?: Partial<Config> } | ||
| { | ||
(options: any): { handler: PluginCreator; config?: Partial<Config> } | ||
__isOptionsFunction: true | ||
} | ||
)[] | ||
@@ -327,11 +379,12 @@ // Top level config related | ||
separator: Partial<SeparatorConfig> | ||
safelist: Partial<SafelistConfig> | ||
presets: Partial<PresetsConfig> | ||
safelist: Array<SafelistConfig> | ||
blocklist: Array<BlocklistConfig> | ||
presets: Array<PresetsConfig> | ||
future: Partial<FutureConfig> | ||
experimental: Partial<ExperimentalConfig> | ||
darkMode: Partial<DarkModeConfig> | ||
theme: Partial<ThemeConfig> | ||
theme: Partial<CustomThemeConfig & { extend: Partial<CustomThemeConfig> }> | ||
corePlugins: Partial<CorePluginsConfig> | ||
plugins: Partial<PluginsConfig> | ||
/** Custom */ | ||
// Custom | ||
[key: string]: any | ||
@@ -338,0 +391,0 @@ } |
@@ -18,2 +18,3 @@ export interface DefaultColors { | ||
'900': '#0f172a' | ||
'950': '#020617' | ||
} | ||
@@ -31,2 +32,3 @@ gray: { | ||
'900': '#111827' | ||
'950': '#030712' | ||
} | ||
@@ -44,2 +46,3 @@ zinc: { | ||
'900': '#18181b' | ||
'950': '#09090b' | ||
} | ||
@@ -57,2 +60,3 @@ neutral: { | ||
'900': '#171717' | ||
'950': '#0a0a0a' | ||
} | ||
@@ -70,2 +74,3 @@ stone: { | ||
'900': '#1c1917' | ||
'950': '#0c0a09' | ||
} | ||
@@ -83,2 +88,3 @@ red: { | ||
'900': '#7f1d1d' | ||
'950': '#450a0a' | ||
} | ||
@@ -96,2 +102,3 @@ orange: { | ||
'900': '#7c2d12' | ||
'950': '#431407' | ||
} | ||
@@ -109,2 +116,3 @@ amber: { | ||
'900': '#78350f' | ||
'950': '#451a03' | ||
} | ||
@@ -122,2 +130,3 @@ yellow: { | ||
'900': '#713f12' | ||
'950': '#422006' | ||
} | ||
@@ -135,2 +144,3 @@ lime: { | ||
'900': '#365314' | ||
'950': '#1a2e05' | ||
} | ||
@@ -148,2 +158,3 @@ green: { | ||
'900': '#14532d' | ||
'950': '#052e16' | ||
} | ||
@@ -161,2 +172,3 @@ emerald: { | ||
'900': '#064e3b' | ||
'950': '#022c22' | ||
} | ||
@@ -174,2 +186,3 @@ teal: { | ||
'900': '#134e4a' | ||
'950': '#042f2e' | ||
} | ||
@@ -187,2 +200,3 @@ cyan: { | ||
'900': '#164e63' | ||
'950': '#083344' | ||
} | ||
@@ -200,2 +214,3 @@ sky: { | ||
'900': '#0c4a6e' | ||
'950': '#082f49' | ||
} | ||
@@ -213,2 +228,3 @@ blue: { | ||
'900': '#1e3a8a' | ||
'950': '#172554' | ||
} | ||
@@ -226,2 +242,3 @@ indigo: { | ||
'900': '#312e81' | ||
'950': '#1e1b4b' | ||
} | ||
@@ -239,2 +256,3 @@ violet: { | ||
'900': '#4c1d95' | ||
'950': '#2e1065' | ||
} | ||
@@ -252,2 +270,3 @@ purple: { | ||
'900': '#581c87' | ||
'950': '#3b0764' | ||
} | ||
@@ -265,2 +284,3 @@ fuchsia: { | ||
'900': '#701a75' | ||
'950': '#4a044e' | ||
} | ||
@@ -278,2 +298,3 @@ pink: { | ||
'900': '#831843' | ||
'950': '#500724' | ||
} | ||
@@ -291,2 +312,3 @@ rose: { | ||
'900': '#881337' | ||
'950': '#4c0519' | ||
} | ||
@@ -293,0 +315,0 @@ /** @deprecated As of Tailwind CSS v2.2, `lightBlue` has been renamed to `sky`. Update your configuration file to silence this warning. */ lightBlue: DefaultColors['sky'] |
@@ -1,1 +0,1 @@ | ||
export type CorePluginList = 'preflight' | 'container' | 'accessibility' | 'pointerEvents' | 'visibility' | 'position' | 'inset' | 'isolation' | 'zIndex' | 'order' | 'gridColumn' | 'gridColumnStart' | 'gridColumnEnd' | 'gridRow' | 'gridRowStart' | 'gridRowEnd' | 'float' | 'clear' | 'margin' | 'boxSizing' | 'display' | 'aspectRatio' | 'height' | 'maxHeight' | 'minHeight' | 'width' | 'minWidth' | 'maxWidth' | 'flex' | 'flexShrink' | 'flexGrow' | 'flexBasis' | 'tableLayout' | 'borderCollapse' | 'borderSpacing' | 'transformOrigin' | 'translate' | 'rotate' | 'skew' | 'scale' | 'transform' | 'animation' | 'cursor' | 'touchAction' | 'userSelect' | 'resize' | 'scrollSnapType' | 'scrollSnapAlign' | 'scrollSnapStop' | 'scrollMargin' | 'scrollPadding' | 'listStylePosition' | 'listStyleType' | 'appearance' | 'columns' | 'breakBefore' | 'breakInside' | 'breakAfter' | 'gridAutoColumns' | 'gridAutoFlow' | 'gridAutoRows' | 'gridTemplateColumns' | 'gridTemplateRows' | 'flexDirection' | 'flexWrap' | 'placeContent' | 'placeItems' | 'alignContent' | 'alignItems' | 'justifyContent' | 'justifyItems' | 'gap' | 'space' | 'divideWidth' | 'divideStyle' | 'divideColor' | 'divideOpacity' | 'placeSelf' | 'alignSelf' | 'justifySelf' | 'overflow' | 'overscrollBehavior' | 'scrollBehavior' | 'textOverflow' | 'whitespace' | 'wordBreak' | 'borderRadius' | 'borderWidth' | 'borderStyle' | 'borderColor' | 'borderOpacity' | 'backgroundColor' | 'backgroundOpacity' | 'backgroundImage' | 'gradientColorStops' | 'boxDecorationBreak' | 'backgroundSize' | 'backgroundAttachment' | 'backgroundClip' | 'backgroundPosition' | 'backgroundRepeat' | 'backgroundOrigin' | 'fill' | 'stroke' | 'strokeWidth' | 'objectFit' | 'objectPosition' | 'padding' | 'textAlign' | 'textIndent' | 'verticalAlign' | 'fontFamily' | 'fontSize' | 'fontWeight' | 'textTransform' | 'fontStyle' | 'fontVariantNumeric' | 'lineHeight' | 'letterSpacing' | 'textColor' | 'textOpacity' | 'textDecoration' | 'textDecorationColor' | 'textDecorationStyle' | 'textDecorationThickness' | 'textUnderlineOffset' | 'fontSmoothing' | 'placeholderColor' | 'placeholderOpacity' | 'caretColor' | 'accentColor' | 'opacity' | 'backgroundBlendMode' | 'mixBlendMode' | 'boxShadow' | 'boxShadowColor' | 'outlineStyle' | 'outlineWidth' | 'outlineOffset' | 'outlineColor' | 'ringWidth' | 'ringColor' | 'ringOpacity' | 'ringOffsetWidth' | 'ringOffsetColor' | 'blur' | 'brightness' | 'contrast' | 'dropShadow' | 'grayscale' | 'hueRotate' | 'invert' | 'saturate' | 'sepia' | 'filter' | 'backdropBlur' | 'backdropBrightness' | 'backdropContrast' | 'backdropGrayscale' | 'backdropHueRotate' | 'backdropInvert' | 'backdropOpacity' | 'backdropSaturate' | 'backdropSepia' | 'backdropFilter' | 'transitionProperty' | 'transitionDelay' | 'transitionDuration' | 'transitionTimingFunction' | 'willChange' | 'content' | ||
export type CorePluginList = 'preflight' | 'container' | 'accessibility' | 'pointerEvents' | 'visibility' | 'position' | 'inset' | 'isolation' | 'zIndex' | 'order' | 'gridColumn' | 'gridColumnStart' | 'gridColumnEnd' | 'gridRow' | 'gridRowStart' | 'gridRowEnd' | 'float' | 'clear' | 'margin' | 'boxSizing' | 'lineClamp' | 'display' | 'aspectRatio' | 'size' | 'height' | 'maxHeight' | 'minHeight' | 'width' | 'minWidth' | 'maxWidth' | 'flex' | 'flexShrink' | 'flexGrow' | 'flexBasis' | 'tableLayout' | 'captionSide' | 'borderCollapse' | 'borderSpacing' | 'transformOrigin' | 'translate' | 'rotate' | 'skew' | 'scale' | 'transform' | 'animation' | 'cursor' | 'touchAction' | 'userSelect' | 'resize' | 'scrollSnapType' | 'scrollSnapAlign' | 'scrollSnapStop' | 'scrollMargin' | 'scrollPadding' | 'listStylePosition' | 'listStyleType' | 'listStyleImage' | 'appearance' | 'columns' | 'breakBefore' | 'breakInside' | 'breakAfter' | 'gridAutoColumns' | 'gridAutoFlow' | 'gridAutoRows' | 'gridTemplateColumns' | 'gridTemplateRows' | 'flexDirection' | 'flexWrap' | 'placeContent' | 'placeItems' | 'alignContent' | 'alignItems' | 'justifyContent' | 'justifyItems' | 'gap' | 'space' | 'divideWidth' | 'divideStyle' | 'divideColor' | 'divideOpacity' | 'placeSelf' | 'alignSelf' | 'justifySelf' | 'overflow' | 'overscrollBehavior' | 'scrollBehavior' | 'textOverflow' | 'hyphens' | 'whitespace' | 'textWrap' | 'wordBreak' | 'borderRadius' | 'borderWidth' | 'borderStyle' | 'borderColor' | 'borderOpacity' | 'backgroundColor' | 'backgroundOpacity' | 'backgroundImage' | 'gradientColorStops' | 'boxDecorationBreak' | 'backgroundSize' | 'backgroundAttachment' | 'backgroundClip' | 'backgroundPosition' | 'backgroundRepeat' | 'backgroundOrigin' | 'fill' | 'stroke' | 'strokeWidth' | 'objectFit' | 'objectPosition' | 'padding' | 'textAlign' | 'textIndent' | 'verticalAlign' | 'fontFamily' | 'fontSize' | 'fontWeight' | 'textTransform' | 'fontStyle' | 'fontVariantNumeric' | 'lineHeight' | 'letterSpacing' | 'textColor' | 'textOpacity' | 'textDecoration' | 'textDecorationColor' | 'textDecorationStyle' | 'textDecorationThickness' | 'textUnderlineOffset' | 'fontSmoothing' | 'placeholderColor' | 'placeholderOpacity' | 'caretColor' | 'accentColor' | 'opacity' | 'backgroundBlendMode' | 'mixBlendMode' | 'boxShadow' | 'boxShadowColor' | 'outlineStyle' | 'outlineWidth' | 'outlineOffset' | 'outlineColor' | 'ringWidth' | 'ringColor' | 'ringOpacity' | 'ringOffsetWidth' | 'ringOffsetColor' | 'blur' | 'brightness' | 'contrast' | 'dropShadow' | 'grayscale' | 'hueRotate' | 'invert' | 'saturate' | 'sepia' | 'filter' | 'backdropBlur' | 'backdropBrightness' | 'backdropContrast' | 'backdropGrayscale' | 'backdropHueRotate' | 'backdropInvert' | 'backdropOpacity' | 'backdropSaturate' | 'backdropSepia' | 'backdropFilter' | 'transitionProperty' | 'transitionDelay' | 'transitionDuration' | 'transitionTimingFunction' | 'willChange' | 'contain' | 'content' | 'forcedColorAdjust' |
@@ -1,1 +0,11 @@ | ||
declare namespace tailwindcss {} | ||
import type { PluginCreator } from 'postcss' | ||
import type { Config } from './config.d' | ||
declare const plugin: PluginCreator<string | Config | { config: string | Config }> | ||
declare type _Config = Config | ||
declare namespace plugin { | ||
export type { _Config as Config } | ||
} | ||
export = plugin |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
5477781
251
41
0
19
32669
43
+ Added@alloc/quick-lru@^5.2.0
+ Addedjiti@^1.21.0
+ Addedmicromatch@^4.0.5
+ Addedpostcss-import@^15.1.0
+ Addedsucrase@^3.32.0
+ Added@alloc/quick-lru@5.2.0(transitive)
+ Added@isaacs/cliui@8.0.2(transitive)
+ Added@jridgewell/gen-mapping@0.3.5(transitive)
+ Added@jridgewell/resolve-uri@3.1.2(transitive)
+ Added@jridgewell/set-array@1.2.1(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@jridgewell/trace-mapping@0.3.25(transitive)
+ Added@pkgjs/parseargs@0.11.0(transitive)
+ Addedansi-regex@5.0.16.1.0(transitive)
+ Addedansi-styles@4.3.06.2.1(transitive)
+ Addedany-promise@1.3.0(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcommander@4.1.1(transitive)
+ Addedcross-spawn@7.0.3(transitive)
+ Addedeastasianwidth@0.2.0(transitive)
+ Addedemoji-regex@8.0.09.2.2(transitive)
+ Addedforeground-child@3.3.0(transitive)
+ Addedglob@10.4.5(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedjackspeak@3.4.3(transitive)
+ Addedjiti@1.21.6(transitive)
+ Addedlilconfig@3.1.2(transitive)
+ Addedlines-and-columns@1.2.4(transitive)
+ Addedlru-cache@10.4.3(transitive)
+ Addedminimatch@9.0.5(transitive)
+ Addedminipass@7.1.2(transitive)
+ Addedmz@2.7.0(transitive)
+ Addedobject-assign@4.1.1(transitive)
+ Addedpackage-json-from-dist@1.0.0(transitive)
+ Addedpath-key@3.1.1(transitive)
+ Addedpath-scurry@1.11.1(transitive)
+ Addedpify@2.3.0(transitive)
+ Addedpirates@4.0.6(transitive)
+ Addedpostcss-import@15.1.0(transitive)
+ Addedpostcss-load-config@4.0.2(transitive)
+ Addedpostcss-nested@6.2.0(transitive)
+ Addedread-cache@1.0.0(transitive)
+ Addedshebang-command@2.0.0(transitive)
+ Addedshebang-regex@3.0.0(transitive)
+ Addedsignal-exit@4.1.0(transitive)
+ Addedstring-width@4.2.35.1.2(transitive)
+ Addedstrip-ansi@6.0.17.1.0(transitive)
+ Addedsucrase@3.35.0(transitive)
+ Addedthenify@3.3.1(transitive)
+ Addedthenify-all@1.6.0(transitive)
+ Addedts-interface-checker@0.1.13(transitive)
+ Addedwhich@2.0.2(transitive)
+ Addedwrap-ansi@7.0.08.1.0(transitive)
+ Addedyaml@2.5.1(transitive)
- Removedcolor-name@^1.1.4
- Removeddetective@^5.2.0
- Removedpostcss-value-parser@^4.2.0
- Removedquick-lru@^5.1.1
- Removedacorn@7.4.1(transitive)
- Removedacorn-node@1.8.2(transitive)
- Removedacorn-walk@7.2.0(transitive)
- Removeddefined@1.0.1(transitive)
- Removeddetective@5.2.1(transitive)
- Removedminimist@1.2.8(transitive)
- Removedpostcss-load-config@3.1.4(transitive)
- Removedpostcss-nested@5.0.6(transitive)
- Removedquick-lru@5.1.1(transitive)
- Removedxtend@4.0.2(transitive)
- Removedyaml@1.10.2(transitive)
Updatedarg@^5.0.2
Updatedfast-glob@^3.3.0
Updatedlilconfig@^2.1.0
Updatedpostcss@^8.4.23
Updatedpostcss-js@^4.0.1
Updatedpostcss-load-config@^4.0.1
Updatedpostcss-nested@^6.0.1
Updatedresolve@^1.22.2