From fd0352cc8f71cc49ab98d9e4d314703a9edab412 Mon Sep 17 00:00:00 2001 From: "Xin Du (Clark)" Date: Tue, 29 Dec 2020 12:16:01 +0000 Subject: [PATCH] feat(postcss): support postcss v8 with explict postcss installation (#8546) * feat(postcss): support postcss v8 with explict postcss installation * fix options name * use postcss.vendor to check postcss 8 --- packages/webpack/src/utils/postcss-v8.js | 208 +++++++++++++++++++++ packages/webpack/src/utils/style-loader.js | 12 +- 2 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 packages/webpack/src/utils/postcss-v8.js diff --git a/packages/webpack/src/utils/postcss-v8.js b/packages/webpack/src/utils/postcss-v8.js new file mode 100644 index 0000000000..9bb765a1f9 --- /dev/null +++ b/packages/webpack/src/utils/postcss-v8.js @@ -0,0 +1,208 @@ +import fs from 'fs' +import path from 'path' +import consola from 'consola' +import { defaults, merge, cloneDeep } from 'lodash' +import createResolver from 'postcss-import-resolver' + +import { isPureObject } from '@nuxt/utils' + +export const orderPresets = { + cssnanoLast (names) { + const nanoIndex = names.indexOf('cssnano') + if (nanoIndex !== names.length - 1) { + names.push(names.splice(nanoIndex, 1)[0]) + } + return names + }, + presetEnvLast (names) { + const nanoIndex = names.indexOf('postcss-preset-env') + if (nanoIndex !== names.length - 1) { + names.push(names.splice(nanoIndex, 1)[0]) + } + return names + }, + presetEnvAndCssnanoLast (names) { + return orderPresets.cssnanoLast(orderPresets.presetEnvLast(names)) + } +} + +function postcssConfigFileWarning () { + if (postcssConfigFileWarning.executed) { + return + } + consola.warn('Please use `build.postcss` in your nuxt.config.js instead of an external config file. Support for such files will be removed in Nuxt 3 as they remove all defaults set by Nuxt and can cause severe problems with features like alias resolving inside your CSS.') + postcssConfigFileWarning.executed = true +} + +export default class PostcssConfig { + constructor (buildContext) { + this.buildContext = buildContext + } + + get cssSourceMap () { + return this.buildContext.buildOptions.cssSourceMap + } + + get postcssOptions () { + return this.buildContext.buildOptions.postcss + } + + get postcssImportAlias () { + const alias = { ...this.buildContext.options.alias } + + for (const key in alias) { + if (key.startsWith('~')) { + continue + } + const newKey = '~' + key + if (!alias[newKey]) { + alias[newKey] = alias[key] + } + } + + return alias + } + + get defaultPostcssOptions () { + const { dev, srcDir, rootDir, modulesDir } = this.buildContext.options + return { + plugins: { + // https://github.com/postcss/postcss-import + 'postcss-import': { + resolve: createResolver({ + alias: this.postcssImportAlias, + modules: [srcDir, rootDir, ...modulesDir] + }) + }, + + // https://github.com/postcss/postcss-url + 'postcss-url': {}, + + // https://github.com/csstools/postcss-preset-env + // TODO: enable when https://github.com/csstools/postcss-preset-env/issues/191 gets closed + // 'postcss-preset-env': this.preset || {}, + cssnano: dev + ? false + : { + preset: ['default', { + // Keep quotes in font values to prevent from HEX conversion + // https://github.com/nuxt/nuxt.js/issues/6306 + minifyFontValues: { removeQuotes: false } + }] + } + }, + // Array, String or Function + order: 'presetEnvAndCssnanoLast' + } + } + + searchConfigFile () { + // Search for postCSS config file and use it if exists + // https://github.com/michael-ciniawsky/postcss-load-config + // TODO: Remove in Nuxt 3 + const { srcDir, rootDir } = this.buildContext.options + for (const dir of [srcDir, rootDir]) { + for (const file of [ + 'postcss.config.js', + '.postcssrc.js', + '.postcssrc', + '.postcssrc.json', + '.postcssrc.yaml' + ]) { + const configFile = path.resolve(dir, file) + if (fs.existsSync(configFile)) { + postcssConfigFileWarning() + return configFile + } + } + } + } + + configFromFile () { + const loaderConfig = (this.postcssOptions && this.postcssOptions.config) || {} + loaderConfig.path = loaderConfig.path || this.searchConfigFile() + + if (loaderConfig.path) { + return { + config: loaderConfig + } + } + } + + normalize (postcssOptions) { + // TODO: Remove in Nuxt 3 + if (Array.isArray(postcssOptions)) { + consola.warn('Using an Array as `build.postcss` will be deprecated in Nuxt 3. Please switch to the object' + + ' declaration') + postcssOptions = { plugins: postcssOptions } + } + return postcssOptions + } + + sortPlugins ({ plugins, order }) { + const names = Object.keys(plugins) + if (typeof order === 'string') { + order = orderPresets[order] + } + return typeof order === 'function' ? order(names, orderPresets) : (order || names) + } + + loadPlugins (postcssOptions) { + const { plugins } = postcssOptions + if (isPureObject(plugins)) { + // Map postcss plugins into instances on object mode once + postcssOptions.plugins = this.sortPlugins(postcssOptions) + .map((p) => { + const plugin = this.buildContext.nuxt.resolver.requireModule(p, { paths: [__dirname] }) + const opts = plugins[p] + if (opts === false) { + return false // Disabled + } + return plugin(opts) + }) + .filter(Boolean) + } + } + + config () { + /* istanbul ignore if */ + if (!this.postcssOptions) { + return false + } + + let postcssOptions = this.configFromFile() + if (postcssOptions) { + return { + postcssOptions, + sourceMap: this.cssSourceMap + } + } + + postcssOptions = this.normalize(cloneDeep(this.postcssOptions)) + + // Apply default plugins + if (isPureObject(postcssOptions)) { + if (postcssOptions.preset) { + this.preset = postcssOptions.preset + delete postcssOptions.preset + } + if (Array.isArray(postcssOptions.plugins)) { + defaults(postcssOptions, this.defaultPostcssOptions) + } else { + // Keep the order of default plugins + postcssOptions = merge({}, this.defaultPostcssOptions, postcssOptions) + this.loadPlugins(postcssOptions) + } + + const { execute } = postcssOptions + delete postcssOptions.execute + delete postcssOptions.order + + return { + execute, + postcssOptions, + sourceMap: this.cssSourceMap + } + } + } +} diff --git a/packages/webpack/src/utils/style-loader.js b/packages/webpack/src/utils/style-loader.js index 70c79b4fba..58f44b57dc 100644 --- a/packages/webpack/src/utils/style-loader.js +++ b/packages/webpack/src/utils/style-loader.js @@ -5,6 +5,7 @@ import ExtractCssChunksPlugin from 'extract-css-chunks-webpack-plugin' import { wrapArray } from '@nuxt/utils' import PostcssConfig from './postcss' +import PostcssV8Config from './postcss-v8' export default class StyleLoader { constructor (buildContext, { isServer, perfLoader, resolveModule }) { @@ -13,8 +14,15 @@ export default class StyleLoader { this.perfLoader = perfLoader this.resolveModule = resolveModule - if (buildContext.options.build.postcss) { - this.postcssConfig = new PostcssConfig(buildContext) + const { postcss: postcssOptions } = buildContext.options.build + if (postcssOptions) { + const postcss = require(resolveModule('postcss')) + // postcss >= v8 + if (!postcss.vendor) { + this.postcssConfig = new PostcssV8Config(buildContext) + } else { + this.postcssConfig = new PostcssConfig(buildContext) + } } }