2020-11-06 03:33:14 +01:00
|
|
|
import chalk from 'chalk'
|
2019-12-09 21:08:15 +01:00
|
|
|
import { findConfig } from '../../../../../lib/find-config'
|
2020-03-29 21:07:55 +02:00
|
|
|
import browserslist from 'browserslist'
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
type CssPluginCollection_Array = (string | [string, boolean | object])[]
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
type CssPluginCollection_Object = { [key: string]: object | boolean }
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
type CssPluginCollection =
|
|
|
|
| CssPluginCollection_Array
|
|
|
|
| CssPluginCollection_Object
|
|
|
|
|
|
|
|
type CssPluginShape = [string, object | boolean]
|
|
|
|
|
|
|
|
const genericErrorText = 'Malformed PostCSS Configuration'
|
|
|
|
|
|
|
|
function getError_NullConfig(pluginName: string) {
|
|
|
|
return `${chalk.red.bold(
|
|
|
|
'Error'
|
|
|
|
)}: Your PostCSS configuration for '${pluginName}' cannot have ${chalk.bold(
|
|
|
|
'null'
|
|
|
|
)} configuration.\nTo disable '${pluginName}', pass ${chalk.bold(
|
|
|
|
'false'
|
|
|
|
)}, otherwise, pass ${chalk.bold('true')} or a configuration object.`
|
|
|
|
}
|
|
|
|
|
|
|
|
function isIgnoredPlugin(pluginPath: string): boolean {
|
|
|
|
const ignoredRegex = /(?:^|[\\/])(postcss-modules-values|postcss-modules-scope|postcss-modules-extract-imports|postcss-modules-local-by-default|postcss-modules)(?:[\\/]|$)/i
|
|
|
|
const match = ignoredRegex.exec(pluginPath)
|
|
|
|
if (match == null) {
|
|
|
|
return false
|
2019-12-09 21:08:15 +01:00
|
|
|
}
|
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
const plugin = match.pop()!
|
|
|
|
console.warn(
|
|
|
|
`${chalk.yellow.bold('Warning')}: Please remove the ${chalk.underline(
|
|
|
|
plugin
|
|
|
|
)} plugin from your PostCSS configuration. ` +
|
2020-01-23 21:28:37 +01:00
|
|
|
`This plugin is automatically configured by Next.js.\n` +
|
|
|
|
'Read more: https://err.sh/next.js/postcss-ignored-plugin'
|
2019-12-09 21:08:15 +01:00
|
|
|
)
|
2019-12-11 17:51:10 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadPlugin(
|
|
|
|
dir: string,
|
|
|
|
pluginName: string,
|
|
|
|
options: boolean | object
|
|
|
|
): Promise<import('postcss').AcceptedPlugin | false> {
|
|
|
|
if (options === false || isIgnoredPlugin(pluginName)) {
|
|
|
|
return false
|
|
|
|
}
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
if (options == null) {
|
|
|
|
console.error(getError_NullConfig(pluginName))
|
|
|
|
throw new Error(genericErrorText)
|
|
|
|
}
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2021-01-11 15:43:08 +01:00
|
|
|
const pluginPath = require.resolve(pluginName, { paths: [dir] })
|
2019-12-11 17:51:10 +01:00
|
|
|
if (isIgnoredPlugin(pluginPath)) {
|
|
|
|
return false
|
|
|
|
} else if (options === true) {
|
|
|
|
return require(pluginPath)
|
|
|
|
} else {
|
|
|
|
const keys = Object.keys(options)
|
|
|
|
if (keys.length === 0) {
|
|
|
|
return require(pluginPath)
|
|
|
|
}
|
|
|
|
return require(pluginPath)(options)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-07 22:59:58 +01:00
|
|
|
function getDefaultPlugins(
|
|
|
|
baseDirectory: string,
|
|
|
|
isProduction: boolean
|
|
|
|
): CssPluginCollection {
|
|
|
|
let browsers: any
|
|
|
|
try {
|
|
|
|
browsers = browserslist.loadConfig({
|
|
|
|
path: baseDirectory,
|
|
|
|
env: isProduction ? 'production' : 'development',
|
|
|
|
})
|
|
|
|
} catch {}
|
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
return [
|
2020-03-30 04:01:30 +02:00
|
|
|
require.resolve('next/dist/compiled/postcss-flexbugs-fixes'),
|
2019-12-11 17:51:10 +01:00
|
|
|
[
|
2020-03-30 04:01:30 +02:00
|
|
|
require.resolve('next/dist/compiled/postcss-preset-env'),
|
2019-12-11 17:51:10 +01:00
|
|
|
{
|
2020-01-07 22:59:58 +01:00
|
|
|
browsers: browsers ?? ['defaults'],
|
2019-12-09 21:08:15 +01:00
|
|
|
autoprefixer: {
|
|
|
|
// Disable legacy flexbox support
|
|
|
|
flexbox: 'no-2009',
|
|
|
|
},
|
|
|
|
// Enable CSS features that have shipped to the
|
|
|
|
// web platform, i.e. in 2+ browsers unflagged.
|
|
|
|
stage: 3,
|
2020-01-08 12:06:16 +01:00
|
|
|
features: {
|
|
|
|
'custom-properties': false,
|
|
|
|
},
|
2019-12-09 21:08:15 +01:00
|
|
|
},
|
2019-12-11 17:51:10 +01:00
|
|
|
],
|
|
|
|
]
|
|
|
|
}
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
export async function getPostCssPlugins(
|
2019-12-31 00:53:35 +01:00
|
|
|
dir: string,
|
2020-01-07 22:59:58 +01:00
|
|
|
isProduction: boolean,
|
2019-12-31 00:53:35 +01:00
|
|
|
defaults: boolean = false
|
2019-12-11 17:51:10 +01:00
|
|
|
): Promise<import('postcss').AcceptedPlugin[]> {
|
2019-12-31 00:53:35 +01:00
|
|
|
let config = defaults
|
|
|
|
? null
|
|
|
|
: await findConfig<{ plugins: CssPluginCollection }>(dir, 'postcss')
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
if (config == null) {
|
2020-01-07 22:59:58 +01:00
|
|
|
config = { plugins: getDefaultPlugins(dir, isProduction) }
|
2019-12-11 17:51:10 +01:00
|
|
|
}
|
|
|
|
|
2019-12-23 21:43:38 +01:00
|
|
|
if (typeof config === 'function') {
|
|
|
|
throw new Error(
|
2020-01-23 21:39:50 +01:00
|
|
|
`Your custom PostCSS configuration may not export a function. Please export a plain object instead.\n` +
|
|
|
|
'Read more: https://err.sh/next.js/postcss-function'
|
2019-12-23 21:43:38 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
// Warn user about configuration keys which are not respected
|
2020-05-18 21:24:37 +02:00
|
|
|
const invalidKey = Object.keys(config).find((key) => key !== 'plugins')
|
2019-12-11 17:51:10 +01:00
|
|
|
if (invalidKey) {
|
|
|
|
console.warn(
|
|
|
|
`${chalk.yellow.bold(
|
|
|
|
'Warning'
|
|
|
|
)}: Your PostCSS configuration defines a field which is not supported (\`${invalidKey}\`). ` +
|
|
|
|
`Please remove this configuration value.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enforce the user provided plugins if the configuration file is present
|
|
|
|
let plugins = config.plugins
|
|
|
|
if (plugins == null || typeof plugins !== 'object') {
|
|
|
|
throw new Error(
|
|
|
|
`Your custom PostCSS configuration must export a \`plugins\` key.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Array.isArray(plugins)) {
|
|
|
|
// Capture variable so TypeScript is happy
|
|
|
|
const pc = plugins
|
|
|
|
|
|
|
|
plugins = Object.keys(plugins).reduce((acc, curr) => {
|
|
|
|
const p = pc[curr]
|
|
|
|
if (typeof p === 'undefined') {
|
|
|
|
console.error(getError_NullConfig(curr))
|
|
|
|
throw new Error(genericErrorText)
|
2019-12-09 21:08:15 +01:00
|
|
|
}
|
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
acc.push([curr, p])
|
|
|
|
return acc
|
|
|
|
}, [] as CssPluginCollection_Array)
|
|
|
|
}
|
|
|
|
|
|
|
|
const parsed: CssPluginShape[] = []
|
2020-05-18 21:24:37 +02:00
|
|
|
plugins.forEach((plugin) => {
|
2019-12-11 17:51:10 +01:00
|
|
|
if (plugin == null) {
|
2019-12-09 21:08:15 +01:00
|
|
|
console.warn(
|
2019-12-11 17:51:10 +01:00
|
|
|
`${chalk.yellow.bold('Warning')}: A ${chalk.bold(
|
|
|
|
'null'
|
|
|
|
)} PostCSS plugin was provided. This entry will be ignored.`
|
|
|
|
)
|
|
|
|
} else if (typeof plugin === 'string') {
|
|
|
|
parsed.push([plugin, true])
|
|
|
|
} else if (Array.isArray(plugin)) {
|
|
|
|
const pluginName = plugin[0]
|
|
|
|
const pluginConfig = plugin[1]
|
|
|
|
if (
|
|
|
|
typeof pluginName === 'string' &&
|
|
|
|
(typeof pluginConfig === 'boolean' || typeof pluginConfig === 'object')
|
|
|
|
) {
|
|
|
|
parsed.push([pluginName, pluginConfig])
|
|
|
|
} else {
|
|
|
|
if (typeof pluginName !== 'string') {
|
|
|
|
console.error(
|
|
|
|
`${chalk.red.bold(
|
|
|
|
'Error'
|
|
|
|
)}: A PostCSS Plugin must be provided as a ${chalk.bold(
|
|
|
|
'string'
|
2020-01-23 22:27:07 +01:00
|
|
|
)}. Instead, we got: '${pluginName}'.\n` +
|
|
|
|
'Read more: https://err.sh/next.js/postcss-shape'
|
2019-12-11 17:51:10 +01:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
`${chalk.red.bold(
|
|
|
|
'Error'
|
2020-01-23 22:27:07 +01:00
|
|
|
)}: A PostCSS Plugin was passed as an array but did not provide its configuration ('${pluginName}').\n` +
|
|
|
|
'Read more: https://err.sh/next.js/postcss-shape'
|
2019-12-11 17:51:10 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
throw new Error(genericErrorText)
|
|
|
|
}
|
2020-01-16 06:09:56 +01:00
|
|
|
} else if (typeof plugin === 'function') {
|
|
|
|
console.error(
|
|
|
|
`${chalk.red.bold(
|
|
|
|
'Error'
|
|
|
|
)}: A PostCSS Plugin was passed as a function using require(), but it must be provided as a ${chalk.bold(
|
|
|
|
'string'
|
2020-01-23 22:27:07 +01:00
|
|
|
)}.\nRead more: https://err.sh/next.js/postcss-shape`
|
2020-01-16 06:09:56 +01:00
|
|
|
)
|
|
|
|
throw new Error(genericErrorText)
|
2019-12-11 17:51:10 +01:00
|
|
|
} else {
|
|
|
|
console.error(
|
|
|
|
`${chalk.red.bold(
|
|
|
|
'Error'
|
2020-01-23 22:27:07 +01:00
|
|
|
)}: An unknown PostCSS plugin was provided (${plugin}).\n` +
|
|
|
|
'Read more: https://err.sh/next.js/postcss-shape'
|
2019-12-09 21:08:15 +01:00
|
|
|
)
|
2019-12-11 17:51:10 +01:00
|
|
|
throw new Error(genericErrorText)
|
|
|
|
}
|
|
|
|
})
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
const resolved = await Promise.all(
|
2020-05-18 21:24:37 +02:00
|
|
|
parsed.map((p) => loadPlugin(dir, p[0], p[1]))
|
2019-12-11 17:51:10 +01:00
|
|
|
)
|
|
|
|
const filtered: import('postcss').AcceptedPlugin[] = resolved.filter(
|
|
|
|
Boolean
|
|
|
|
) as import('postcss').AcceptedPlugin[]
|
2019-12-09 21:08:15 +01:00
|
|
|
|
2019-12-11 17:51:10 +01:00
|
|
|
return filtered
|
2019-12-09 21:08:15 +01:00
|
|
|
}
|