| import { createRequire } from 'node:module'; |
| import fs from 'node:fs'; |
| import path from 'node:path'; |
| import postcss from 'postcss'; |
| import defaultPreset from 'cssnano-preset-default'; |
| |
| const require = createRequire(import.meta.url); |
| |
| const configFileNames = ['.cssnanorc.json', 'cssnano.config.js']; |
| |
| /** @typedef {boolean | { exclude?: boolean } | void | undefined} PluginOptions */ |
| /** @typedef {import('postcss').PluginCreator<any>} PluginCreator */ |
| /** @typedef {[PluginCreator, PluginOptions]} PresetPlugin */ |
| /** @typedef {(options?: any) => { plugins: PresetPlugin[] }} PresetFactory */ |
| /** @typedef {string | PresetFactory | [string | PresetFactory, object] | { plugins: PresetPlugin[] }} PresetSpec */ |
| /** @typedef {string | PluginCreator | [string | PluginCreator, object?]} PluginSpec */ |
| /** @typedef {{ preset?: PresetSpec, plugins?: PluginSpec[], configFile?: string }} Options */ |
| /** |
| * @param {string} moduleId |
| * @returns {boolean} |
| */ |
| function isResolvable(moduleId) { |
| try { |
| require.resolve(moduleId); |
| return true; |
| } catch { |
| return false; |
| } |
| } |
| |
| /** |
| * preset can be one of four possibilities: |
| * preset = 'default' |
| * preset = ['default', {}] |
| * preset = function <- to be invoked |
| * preset = {plugins: []} <- already invoked function |
| * |
| * @param {unknown} preset |
| * @return {PresetPlugin[]} |
| */ |
| function resolvePreset(preset) { |
| let fn, options; |
| |
| if (Array.isArray(preset)) { |
| fn = preset[0]; |
| options = preset[1]; |
| } else { |
| fn = preset; |
| options = {}; |
| } |
| |
| // For JS setups where we invoked the preset already |
| if (typeof fn === 'object' && fn !== null && 'plugins' in fn) { |
| return fn.plugins; |
| } |
| |
| // Provide an alias for the default preset, as it is built-in. |
| if (fn === 'default') { |
| return defaultPreset(options).plugins; |
| } |
| |
| // For non-JS setups; we'll need to invoke the preset ourselves. |
| if (typeof fn === 'function') { |
| return fn(options).plugins; |
| } |
| |
| // Try loading a preset from node_modules |
| if (isResolvable(fn)) { |
| return require(fn)(options).plugins; |
| } |
| |
| // Only the built-in preset names support shorthand resolution. Other |
| // strings must be passed to the module resolver unchanged. |
| if (fn === 'lite' || fn === 'advanced') { |
| const sugar = `cssnano-preset-${fn}`; |
| if (isResolvable(sugar)) { |
| return require(sugar)(options).plugins; |
| } |
| } |
| |
| // If all else fails, we probably have a typo in the config somewhere |
| throw new Error( |
| `Cannot load preset "${fn}". Please check your configuration for errors and try again.` |
| ); |
| } |
| |
| /** |
| * @param {string} filePath |
| * @returns {unknown} |
| */ |
| function loadConfigFile(filePath) { |
| if (filePath.endsWith('.json')) { |
| return JSON.parse(fs.readFileSync(filePath, 'utf8')); |
| } |
| const config = createRequire(filePath)(filePath); |
| if ( |
| config !== null && |
| typeof config === 'object' && |
| Object.prototype.toString.call(config) === '[object Module]' && |
| 'default' in config |
| ) { |
| return config.default; |
| } |
| return config; |
| } |
| |
| /** |
| * @param {string} [configFile] |
| * @returns {unknown | null} |
| */ |
| function loadDiscoveredConfig(configFile) { |
| if (configFile) { |
| const configPath = path.resolve(process.cwd(), configFile); |
| if (!fs.existsSync(configPath)) { |
| throw new Error( |
| `Cannot find cssnano configuration file "${configFile}".` |
| ); |
| } |
| return loadConfigFile(configPath); |
| } |
| |
| for (const fileName of configFileNames) { |
| const configPath = path.join(process.cwd(), fileName); |
| if (!fs.existsSync(configPath)) continue; |
| return loadConfigFile(configPath); |
| } |
| return null; |
| } |
| |
| /** |
| * @param {Options} options |
| * @returns {PresetPlugin[]} |
| */ |
| function resolveConfig(options) { |
| if (options.preset) { |
| if ( |
| Array.isArray(options.preset) && |
| /** @type {unknown[]} */ (options.preset).length === 0 |
| ) { |
| return []; |
| } |
| return resolvePreset(options.preset); |
| } |
| if (Array.isArray(options.plugins)) return []; |
| |
| const config = loadDiscoveredConfig(options.configFile); |
| const preset = |
| config !== null && typeof config === 'object' && 'preset' in config |
| ? config.preset |
| : config; |
| return resolvePreset(config === null ? 'default' : preset); |
| } |
| |
| /** |
| * @param {Options=} options |
| * @return {import('postcss').Processor} |
| */ |
| function cssnanoPlugin(options = {}) { |
| let nanoPlugins = resolveConfig(options); |
| if (Array.isArray(options.plugins)) { |
| nanoPlugins = nanoPlugins.slice(); |
| const inputPlugins = options.plugins; |
| for (const plugin of inputPlugins) { |
| if (Array.isArray(plugin)) { |
| const [pluginDef, opts = {}] = plugin; |
| if (typeof pluginDef === 'string' && isResolvable(pluginDef)) { |
| nanoPlugins.push([ |
| /** @type {PluginCreator} */ (require(pluginDef)), |
| opts, |
| ]); |
| } else { |
| nanoPlugins.push([/** @type {PluginCreator} */ (pluginDef), opts]); |
| } |
| } else if (typeof plugin === 'string' && isResolvable(plugin)) { |
| nanoPlugins.push([/** @type {PluginCreator} */ (require(plugin)), {}]); |
| } else { |
| nanoPlugins.push([/** @type {PluginCreator} */ (plugin), {}]); |
| } |
| } |
| } |
| const plugins = []; |
| for (const nanoPlugin of nanoPlugins) { |
| if (Array.isArray(nanoPlugin)) { |
| const [processor, opts] = nanoPlugin; |
| if ( |
| typeof opts === 'undefined' || |
| (typeof opts === 'object' && !opts.exclude) || |
| (typeof opts === 'boolean' && opts === true) |
| ) { |
| plugins.push(processor(opts)); |
| } |
| } else { |
| plugins.push(nanoPlugin); |
| } |
| } |
| return postcss(plugins); |
| } |
| |
| /** @type {true} */ |
| cssnanoPlugin.postcss = true; |
| |
| export { cssnanoPlugin as default, cssnanoPlugin as 'module.exports' }; |