diff --git a/packages/webpack/src/config/utils/postcss.js b/packages/webpack/src/config/utils/postcss.js index 0d011ecc0c..a96caedee6 100644 --- a/packages/webpack/src/config/utils/postcss.js +++ b/packages/webpack/src/config/utils/postcss.js @@ -7,6 +7,16 @@ import createResolver from 'postcss-import-resolver' import { isPureObject } from '@nuxt/common' +export const orderPresets = { + cssnanoLast: (names) => { + const nanoIndex = names.indexOf('cssnano') + if (nanoIndex !== names.length - 1) { + names.push(names.splice(nanoIndex, 1)[0]) + } + return names + } +} + export default class PostcssConfig { constructor(options, nuxt) { this.nuxt = nuxt @@ -45,7 +55,9 @@ export default class PostcssConfig { // https://github.com/csstools/postcss-preset-env 'postcss-preset-env': this.preset || {}, 'cssnano': this.dev ? false : { preset: 'default' } - } + }, + // Array, String or Function + order: 'cssnanoLast' } } @@ -87,11 +99,19 @@ export default class PostcssConfig { return config } + 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(config) { const plugins = config.plugins if (isPureObject(plugins)) { // Map postcss plugins into instances on object mode once - config.plugins = Object.keys(plugins) + config.plugins = this.sortPlugins(config) .map((p) => { const plugin = require(p) const opts = plugins[p] diff --git a/packages/webpack/src/config/utils/style-loader.js b/packages/webpack/src/config/utils/style-loader.js index daba33459b..3877be4c76 100644 --- a/packages/webpack/src/config/utils/style-loader.js +++ b/packages/webpack/src/config/utils/style-loader.js @@ -14,7 +14,10 @@ export default class StyleLoader { this.assetsDir = options.dir.assets this.staticDir = options.dir.static this.rootDir = options.rootDir - this.loaders = options.build.loaders + this.loaders = { + css: options.build.loaders.css, + cssModules: options.build.loaders.cssModules + } this.extractCSS = options.build.extractCSS this.resources = options.build.styleResources this.sourceMap = Boolean(options.build.cssSourceMap) @@ -91,13 +94,12 @@ export default class StyleLoader { apply(ext, loaders = []) { const customLoaders = [].concat( - this.postcss(loaders), + this.postcss(), this.normalize(loaders), this.styleResource(ext) ).filter(Boolean) - const { css: cssOptions, cssModules: cssModulesOptions } = this.loaders - cssOptions.importLoaders = cssModulesOptions.importLoaders = customLoaders.length + this.loaders.css.importLoaders = this.loaders.cssModules.importLoaders = customLoaders.length const styleLoader = this.extract() || this.vueStyle() @@ -107,7 +109,7 @@ export default class StyleLoader { resourceQuery: /module/, use: this.perfLoader.css().concat( styleLoader, - this.cssModules(cssModulesOptions), + this.cssModules(this.loaders.cssModules), customLoaders ) }, @@ -115,7 +117,7 @@ export default class StyleLoader { { use: this.perfLoader.css().concat( styleLoader, - this.css(cssOptions), + this.css(this.loaders.css), customLoaders ) } diff --git a/test/fixtures/basic/nuxt.config.js b/test/fixtures/basic/nuxt.config.js index 0c716e5be0..b62044c83e 100644 --- a/test/fixtures/basic/nuxt.config.js +++ b/test/fixtures/basic/nuxt.config.js @@ -77,7 +77,8 @@ export default { } }, plugins: { - cssnano: {} + cssnano: {}, + [path.resolve(__dirname, 'plugins', 'tailwind.js')]: {} } } } diff --git a/test/fixtures/basic/plugins/tailwind.js b/test/fixtures/basic/plugins/tailwind.js new file mode 100644 index 0000000000..b3d0b7a308 --- /dev/null +++ b/test/fixtures/basic/plugins/tailwind.js @@ -0,0 +1,6 @@ + +const postcss = require('postcss') + +module.exports = postcss.plugin('nuxt-test', () => { + return function () {} +}) diff --git a/test/unit/basic.dev.test.js b/test/unit/basic.dev.test.js index c6dd6cb6e3..2cac236062 100644 --- a/test/unit/basic.dev.test.js +++ b/test/unit/basic.dev.test.js @@ -10,6 +10,7 @@ let transpile = null let output = null let loadersOptions let vueLoader +let postcssLoader describe('basic dev', () => { beforeAll(async () => { @@ -40,6 +41,8 @@ describe('basic dev', () => { output = wpOutput loadersOptions = loaders vueLoader = rules.find(loader => loader.test.test('.vue')) + const cssLoaders = rules.find(loader => loader.test.test('.css')).oneOf[0].use + postcssLoader = cssLoaders[cssLoaders.length - 1] } } } @@ -82,6 +85,19 @@ describe('basic dev', () => { expect(vueLoader.options).toBe(vue) }) + test('Config: cssnano is at then end of postcss plugins', () => { + const plugins = postcssLoader.options.plugins.map((plugin) => { + return plugin.postcssPlugin + }) + expect(plugins).toEqual([ + 'postcss-import', + 'postcss-url', + 'postcss-preset-env', + 'nuxt-test', + 'cssnano' + ]) + }) + test('/stateless', async () => { const window = await nuxt.server.renderAndGetWindow(url('/stateless')) const html = window.document.body.innerHTML