'use strict'; const path = require('path'); /** @type {any} */ const postcss = require('postcss'); const yaml = require('yaml'); const { lilconfigSync } = require('lilconfig'); const cssnano = 'cssnano'; /** @typedef {{preset?: any, plugins?: any[], configFile?: string}} Options */ /** * @param {string} moduleId * @returns {boolean} */ function isResolvable(moduleId) { try { require.resolve(moduleId); return true; } catch (e) { return false; } } /** * preset can be one of four possibilities: * preset = 'default' * preset = ['default', {}] * preset = function <- to be invoked * preset = {plugins: []} <- already invoked function * * @param {any} preset * @return {[import('postcss').PluginCreator, boolean | Record | undefined][]}} */ 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 (preset.plugins) { return preset.plugins; } // Provide an alias for the default preset, as it is built-in. if (fn === 'default') { return require('cssnano-preset-default')(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; } const sugar = `cssnano-preset-${fn}`; // Try loading a preset from node_modules (sugar) 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.` ); } /** * cssnano will look for configuration firstly as options passed * directly to it, and failing this it will use lilconfig to * load an external file. * @param {Options} options */ function resolveConfig(options) { if (options.preset) { return resolvePreset(options.preset); } /** @type {string | undefined} */ let searchPath = process.cwd(); let configPath = undefined; if (options.configFile) { searchPath = undefined; configPath = path.resolve(process.cwd(), options.configFile); } const configExplorer = lilconfigSync(cssnano, { searchPlaces: [ 'package.json', '.cssnanorc', '.cssnanorc.json', '.cssnanorc.yaml', '.cssnanorc.yml', '.cssnanorc.js', 'cssnano.config.js', ], loaders: { '.yaml': (filepath, content) => yaml.parse(content), '.yml': (filepath, content) => yaml.parse(content), }, }); const config = configPath ? configExplorer.load(configPath) : configExplorer.search(searchPath); if (config === null) { return resolvePreset('default'); } return resolvePreset(config.config.preset || config.config); } /** * @type {import('postcss').PluginCreator} * @param {Options=} options * @return {import('postcss').Plugin} */ function cssnanoPlugin(options = {}) { if (Array.isArray(options.plugins)) { if (!options.preset || !options.preset.plugins) { options.preset = { plugins: [] }; } options.plugins.forEach((plugin) => { if (Array.isArray(plugin)) { const [pluginDef, opts = {}] = plugin; if (typeof pluginDef === 'string' && isResolvable(pluginDef)) { options.preset.plugins.push([require(pluginDef), opts]); } else { options.preset.plugins.push([pluginDef, opts]); } } else if (typeof plugin === 'string' && isResolvable(plugin)) { options.preset.plugins.push([require(plugin), {}]); } else { options.preset.plugins.push([plugin, {}]); } }); } const plugins = []; const nanoPlugins = resolveConfig(options); 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); } cssnanoPlugin.postcss = true; module.exports = cssnanoPlugin;