diff --git a/sources/@repo/docs/content/extensions/bud-postcss.mdx b/sources/@repo/docs/content/extensions/bud-postcss.mdx index 49a9843f64..04bcd51dad 100644 --- a/sources/@repo/docs/content/extensions/bud-postcss.mdx +++ b/sources/@repo/docs/content/extensions/bud-postcss.mdx @@ -14,7 +14,7 @@ import {Install} from '@site/src/docs/Install' ## Usage -By default, **@roots/bud-postcss** includes the following plugins, registered with postcss in this order: +By default, **@roots/bud-postcss** includes the following plugins, registered with PostCSS in this order: | Order | Key | Plugin | | ----- | --------- | ----------------------------------------------------------- | @@ -22,150 +22,124 @@ By default, **@roots/bud-postcss** includes the following plugins, registered wi | 2. | 'nesting' | [postcss-nested](https://github.com/postcss/postcss-nested) | | 3. | 'env' | [postcss-preset-env](https://preset-env.cssdb.org/) | -If this works for you, great! No need to keep reading. But, should you need something more specialized, -you can configure it in your bud config file or a postcss.config.js file in your project's root directory. +If this works for you, great! No need to keep reading. -Any additional plugins you may want to add will be added between `nesting` and `env`. -This is, generally, what you want, but there instructions included below for overriding this behavior. +Should you need something more specialized, you can configure PostCSS in your bud config file or a +standard PostCSS config file. -## Add a plugin +Note that by using a PostCSS config file you will be overriding the bud.js options API. -You can add a plugin with **bud.postcss.setPlugin**. This function takes up to two parameters. +## Overriding PostCSS options directly -The first parameter is required. It is the name for the plugin. -If it is the only parameter being passed, it should be the module signifier. +You can set the PostCSS options directly using `bud.postcss.set`. + +PostCss options are contained in the `postcssOptions` key. ```typescript title=bud.config.js -bud.postcss.setPlugin('example-postcss-plugin') +bud.postcss.set(`postcssOptions`, { + parser: `sugarss`, + plugins: [`postcss-import`, `postcss-nested`, `postcss-preset-env`], +}) ``` -The second parameter is optional and multivariate. +Doing it this way is quick and easy but you are fully overriding the default settings. It's best to use this method only +if you are comfortable with PostCSS and know what you are doing. -Use a string to specify the plugin's resolvable path: +If you want to just change a single option you can use dot syntax: ```typescript title=bud.config.js -bud.postcss.setPlugin( - 'example-postcss-plugin', - bud.path('./directory/example.js'), -) +bud.postcss.set(`postcssOptions.parser`, `sugarss`) ``` -Use an array to set any associated options alongside the resolvable path. +This allows the default options to be merged with your new options. -```typescript title=bud.config.js -bud.postcss.setPlugin('example-postcss-plugin', [ - bud.path('./directory/example.js'), - {stage: 1}, -]) -``` +## Adding a plugin -## Override plugin options +Adding a plugin with the API is a two step process: -You can modify options for a plugin using **bud.postcss.setPluginOptions**. +1. Register the plugin +2. Add it to the plugin order -The first parameter is the plugin key and the second is the options object. +Let's look at how this is done using `tailwindcss` as an example. ```typescript title=bud.config.js -bud.postcss.setPluginOptions('example-postcss-plugin', {optimize: false}) +bud.postcss + /** Register the tailwindcss/nesting plugin */ + .setPlugin(`tailwindcss/nesting`) + /** Register the tailwindcss plugin and options */ + .setPlugin(`tailwindcss`) + /** Specify which registered plugins you want to use, in the order they should be added */ + .use([`import`, `tailwindcss/nesting`, `tailwindcss`, `env`]) ``` -## Override plugin resolution - -You can modify the path for a postcss plugin using **bud.postcss.setPluginPath**. - -The first parameter is the plugin key and the second is the new path to assign. +### Step 1. Registration ```typescript title=bud.config.js -bud.postcss.setPluginPath( - 'example-postcss-plugin', - bud.path('./lib/my-plugin.js'), -) +bud.postcss.setPlugin(`example-postcss-plugin`) ``` -## Add multiple plugins - -You can add multiple postcss plugins using **bud.postcss.setPlugins**. - -### Using an object +If you want to be specific about how to resolve a plugin, you can pass a second parameter. ```typescript title=bud.config.js -bud.postcss.setPlugins({ - ['tailwindcss']: await bud.module.resolve('tailwindcss'), - ['nesting']: await bud.module.resolve('tailwindcss/nesting/index.js'), -}) +bud.postcss.setPlugin( + `example-postcss-plugin`, + bud.path(`@modules/example-postcss-plugin/lib/index.js`), +) ``` -### Using a Map +If you want to pass along options with the plugin, you can do so by passing an array. ```typescript title=bud.config.js -bud.postcss.setPlugins( - new Map([]) - .set('tailwindcss', await bud.module.resolve('tailwindcss')) - .set('nesting', [ - await bud.module.resolve('tailwindcss/nesting/index.js'), - ]), -) +bud.postcss.setPlugin(`example-postcss-plugin`, [ + `example-postcss-plugin`, + {stage: 1}, +]) ``` -### Using an array +### Step 2. Add the plugin ```typescript title=bud.config.js -bud.postcss.setPlugins([ - ['tailwindcss', await bud.module.resolve('tailwindcss')], - ['nesting', await bud.module.resolve('tailwindcss/nesting/index.js')], +bud.postcss.use([ + `import`, + `nesting`, + `example-postcss-plugin`, // our new plugin + 'env', ]) ``` -## Remove a plugin - -You may remove a plugin with **bud.postcss.unsetPlugin**. +You can also use a callback: ```typescript title=bud.config.js -bud.postcss.unsetPlugin('import') +bud.postcss.use(plugins => [...plugins, 'example-postcss-plugin']) ``` -## Plugin ordering - -**@roots/bud-postcss** loosely attempts to enforce a particular plugin load order. - -It will always attempt to load plugins in this order: +## Override plugin options -| Order | Description | Default | -| ----- | ------------------------------- | -------------------- | -| 1 | `import` implementation | `postcss-import` | -| 2 | `nesting` implementation | `postcss-nested` | -| ...3 | all other plugins as registered | none | -| 4 | `env` implementation | `postcss-preset-env` | +You can modify options for a plugin using **bud.postcss.setPluginOptions**. -The reason bud.js gets so hands-on here is to provide a reasonable default which people can build on without needing to splice in their own plugins manually. -If this is a problem for your project, the most direct way to override this is to hook a function to `postcss.plugins` which returns an array of plugin values: +The first parameter is the plugin key and the second is the options object. ```typescript title=bud.config.js -bud.hooks.on('postcss.plugins', () => { - return [ - bud.postcss.get('import'), - // ... plugins, in the order you wish to load them - ] -}) +bud.postcss.setPluginOptions('example-postcss-plugin', {optimize: false}) ``` -## Working with the plugin registry directly +## Override plugin path + +You can modify the path for a PostCSS plugin using **bud.postcss.setPluginPath**. -**@roots/bud-postcss** stores PostCSS plugins as a `Map`. You can work with this `Map` directly using **bud.postcss.plugins**. +The first parameter is the plugin key and the second is the new path to assign. ```typescript title=bud.config.js -export default async bud => { - bud.postcss.plugins.set('import', [ - await module.resolve('postcss-import'), - {...options}, - ]) +bud.postcss.setPluginPath( + 'example-postcss-plugin', + bud.path('./lib/my-plugin.js'), +) +``` - bud.postcss.plugins.get('import') +## Remove a plugin - bud.postcss.plugins.delete('import') +You may remove a plugin by calling **bud.postcss.unsetPlugin** with the plugin key. - bud.postcss.plugins.clear() -} +```typescript title=bud.config.js +bud.postcss.unsetPlugin(`import`) ``` - -When you `get` something, you can hopefully rely on it being a `tuple` with the plugin in the first position and the options in the second. The plugin might be a string (resolveable path) or a module. diff --git a/sources/@roots/bud-postcss/src/extension.ts b/sources/@roots/bud-postcss/src/extension.ts index 8c15022c78..ccfc69b07d 100644 --- a/sources/@roots/bud-postcss/src/extension.ts +++ b/sources/@roots/bud-postcss/src/extension.ts @@ -6,9 +6,8 @@ import { label, options, } from '@roots/bud-framework/extension/decorators' -import {deprecated} from '@roots/bud-support/decorators' +import {deprecated} from '@roots/bud-support/decorators/deprecated' import {InputError} from '@roots/bud-support/errors' -import isFunction from '@roots/bud-support/lodash/isFunction' import isUndefined from '@roots/bud-support/lodash/isUndefined' import type {Plugin, Processor} from 'postcss' @@ -21,7 +20,6 @@ type Input = type InputList = Array type InputRecords = Record type InputMap = Map -type Registry = Map /** * PostCSS configuration options @@ -34,6 +32,8 @@ interface Options { parser?: string plugins?: InputList } + plugins: InputRecords + order: Array<`${keyof InputRecords & string}`> } /** @@ -48,31 +48,28 @@ interface Options { @expose(`postcss`) export class BudPostCss extends Extension { /** - * Boolean representing if project has a postcss config file + * Config file options */ - public get overridenByProjectConfigFile() { - if (!this.app.context.files) return false - - return Object.values(this.app.context.files).some( - file => file?.name?.includes(`postcss`) && file?.module, - ) - } - - /** - * PostCSS configuration options (if overridden by project config file) - */ - public declare configFileOptions: Record | undefined + protected configFileOptions: Options[`postcssOptions`] /** * {@link Extension.register} */ @bind public override async register({build, context}: Bud): Promise { - if (!this.overridenByProjectConfigFile) { - this.setPlugins({ - import: await this.resolve(`postcss-import`, import.meta.url), - nesting: await this.resolve(`postcss-nested`, import.meta.url), - env: [ + if ( + !context.files || + !Object.values(context.files).some( + file => file?.name?.includes(`postcss`) && file?.module, + ) + ) { + this.setPlugin(`import`, [ + await this.resolve(`postcss-import`, import.meta.url), + ]) + .setPlugin(`nesting`, [ + await this.resolve(`postcss-nested`, import.meta.url), + ]) + .setPlugin(`env`, [ await this.resolve(`postcss-preset-env`, import.meta.url).then( path => path.replace(`.mjs`, `.cjs`), ), @@ -82,16 +79,16 @@ export class BudPostCss extends Extension { 'focus-within-pseudo-class': false, }, }, - ], - }) + ]) + .use([`import`, `nesting`, `env`]) } else { this.logger.log( `PostCSS configuration is being overridden by project configuration file.`, ) - const file = Object.values(context.files).find( - ({name, module}) => name.includes(`postcss`) && module, - ) - this.configFileOptions = file?.module?.default ?? file?.module + this.set(`postcssOptions.config`, true) + this.configFileOptions = Object.values(this.app.context.files).find( + file => file?.name?.includes(`postcss`) && file?.module, + )?.module } build @@ -102,7 +99,7 @@ export class BudPostCss extends Extension { .setItem(`postcss`, { loader: `postcss`, options: () => ({ - postcssOptions: this.configFileOptions || this.postcssOptions, + postcssOptions: this.configFileOptions ?? this.postcssOptions, sourceMap: this.get(`sourceMap`), }), }) @@ -116,60 +113,40 @@ export class BudPostCss extends Extension { * @readonly */ public get postcssOptions(): Options[`postcssOptions`] { - let plugins = [] - - if (!this.plugins.size) return null - - this.plugins.has(`import`) && plugins.push(this.plugins.get(`import`)) - - this.plugins.has(`nesting`) && - plugins.push(this.plugins.get(`nesting`)) - - Array.from(this.plugins.entries()) - .filter(([k]) => ![`import`, `nesting`, `env`].includes(k)) - .forEach(([_k, v]: [string, any]) => plugins.push(v)) - - this.plugins.has(`env`) && plugins.push(this.plugins.get(`env`)) - - const options = { - config: this.get(`postcssOptions.config`), - syntax: this.get(`postcssOptions.syntax`), - parser: this.get(`postcssOptions.parser`), - plugins: plugins.filter(Boolean), + const options: Options[`postcssOptions`] = this.get( + `postcssOptions`, + ) as Options[`postcssOptions`] + + if (!options?.plugins?.length) { + options.plugins = this.get(`order`) + .map(name => this.get(`plugins.${name}`)) + .filter(Boolean) as InputList } - this.logger.info(`postcss syntax`, options.syntax) - this.logger.info(`postcss parser`, options.parser) - this.logger.info(`postcss plugins`, options.plugins) + this.logger.info(`postcss options`, options) - return options as Options[`postcssOptions`] + return options } /** - * Plugins registry - */ - protected readonly _plugins: Registry = new Map([]) - - /** - * PostCss plugins accessor - */ - public get plugins() { - return this._plugins - } - /** - * Get plugins + * Use plugins + * + * @remarks + * Sets the plugin order */ - @bind - public getPlugins() { - return this._plugins + public use( + order: Array | ((plugins: Array) => Array), + ) { + this.set(`order`, order) + return this } /** - * Replaces all plugins with provided value + * Set plugins */ @bind public setPlugins(plugins: InputRecords | InputMap | InputList): this { - if (this.overridenByProjectConfigFile) { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.setPlugins will not work as expected\n`, @@ -179,35 +156,56 @@ export class BudPostCss extends Extension { } const pluginMap = (plugin: string | [string, any?]): [string, any?] => - Array.isArray(plugin) ? plugin : [plugin] - - const setPlugin = (plugin: [string, any?]): unknown => - this._plugins.set(...plugin) + Array.isArray(plugin) && plugin.length > 1 + ? plugin + : [Array.isArray(plugin) ? plugin[0] : plugin, undefined] if (Array.isArray(plugins)) { - plugins.map(pluginMap).forEach(setPlugin) + plugins.map(pluginMap).forEach(plugin => this.setPlugin(...plugin)) return this } if (plugins instanceof Map) { - Array.from(plugins.entries()).forEach(([k, v]) => { - this.setPlugin(k, v) + Array.from(plugins.entries()).forEach(plugin => { + this.setPlugin(...plugin) }) return this } - Object.entries(plugins).map(pluginMap).forEach(setPlugin) + Object.entries(plugins) + .map(pluginMap) + .forEach(plugin => this.setPlugin(...plugin)) return this } + /** + * Get a plugin + */ + @bind + public getPlugin(name: string): [string | Plugin | Processor, any?] { + if (this.get(`postcssOptions.config`)) { + this.logger.warn( + `PostCSS configuration is being overridden by project configuration file.\n`, + `bud.postcss.setPlugin will not work as expected\n`, + `tried to get:`, + name, + ) + } + + return this.get(`plugins.${name}`) as [ + string | Plugin | Processor, + any?, + ] + } + /** * Set a plugin */ @bind public setPlugin(name: string, plugin?: Input): this { - if (this.overridenByProjectConfigFile) { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.setPlugin will not work as expected\n`, @@ -218,16 +216,14 @@ export class BudPostCss extends Extension { } if (isUndefined(plugin)) { - this._plugins.set(name, [name]) - return this + plugin = [name, undefined] } - if (Array.isArray(plugin)) { - this._plugins.set(name, plugin) - return this + if (!Array.isArray(plugin)) { + plugin = [plugin, undefined] } - this._plugins.set(name, [plugin]) + this.set(`plugins.${name}`, plugin) return this } @@ -235,17 +231,17 @@ export class BudPostCss extends Extension { * Remove a plugin */ @bind - public unsetPlugin(plugin: string) { - if (this.overridenByProjectConfigFile) { + public unsetPlugin(name: string) { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.unsetPlugin will not work as expected\n`, `tried to unset:`, - plugin, + name, ) } - this.plugins.has(plugin) && this.plugins.delete(plugin) + this.set(`plugins.${name}`, undefined) return this } @@ -254,19 +250,19 @@ export class BudPostCss extends Extension { * Get plugin options */ @bind - public getPluginOptions(plugin: string): Record { - if (this.overridenByProjectConfigFile) { + public getPluginOptions(name: string): Record { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.getPluginOptions will not work as expected\n`, - `tried to get: ${plugin}`, + `tried to get:`, + name, ) } - return this.plugins.get(plugin).length && - this.plugins.get(plugin).length > 1 - ? this.plugins.get(plugin).pop() - : {} + const plugin: any = this.get(`plugins.${name}`) + if (!plugin) throw new Error(`Plugin ${name} does not exist`) + return plugin.length && plugin.length > 1 ? plugin[1] : {} } /** @@ -274,26 +270,28 @@ export class BudPostCss extends Extension { */ @bind public setPluginOptions( - plugin: string, + name: string, options: | Record | ((options: Record) => Record), ): this { - if (this.overridenByProjectConfigFile) { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.setPluginOptions will not work as expected`, ) } - if (!this.plugins.has(plugin)) { - throw new InputError(`${plugin} does not exist`) + const plugin = this.getPlugin(name) + + if (!plugin) { + throw new InputError(`${name} does not exist`) } - this.plugins.set(plugin, [ - this.getPluginPath(plugin), - isFunction(options) - ? options(this.getPluginOptions(plugin)) + this.setPlugin(name, [ + this.getPluginPath(name), + typeof options === `function` + ? options(this.getPluginOptions(name)) : options, ]) @@ -304,35 +302,35 @@ export class BudPostCss extends Extension { * Get plugin path */ @bind - public getPluginPath(plugin: string): string { - if (this.overridenByProjectConfigFile) { + public getPluginPath(name: string): string { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.getPluginPath will not work as expected`, ) } - return this.plugins.has(plugin) && this.plugins.get(plugin)?.length - ? [...this.plugins.get(plugin)].shift() - : this.plugins.get(plugin) + const plugin: any = this.get(`plugins.${name}`) + return plugin && plugin?.length ? [...plugin][0] : plugin } /** * Set plugin path */ @bind - public setPluginPath(plugin: string, path: string): this { - if (this.overridenByProjectConfigFile) { + public setPluginPath(name: string, path: string): this { + if (this.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration is being overridden by project configuration file.\n`, `bud.postcss.setPluginPath will not work as expected`, ) } - const target = this.plugins.get(plugin) - const hasOptions = target.length && target.length > 1 + const plugin: any = this.get(`plugins.${name}`) + if (!plugin) throw new Error(`Plugin ${name} does not exist`) - this.setPlugin(plugin, hasOptions ? [path, target.pop()] : [path]) + const hasOptions = plugin.length && plugin.length > 1 + this.set(`plugins.${name}`, hasOptions ? [path, plugin[1]] : [path]) return this } diff --git a/sources/@roots/bud-postcss/src/types.ts b/sources/@roots/bud-postcss/src/types.ts index 2ef81e9c0e..e49f184439 100644 --- a/sources/@roots/bud-postcss/src/types.ts +++ b/sources/@roots/bud-postcss/src/types.ts @@ -8,16 +8,16 @@ import type {PublicExtensionApi} from '@roots/bud-framework/extension' import type {BudPostCss} from './extension.js' interface PublicPostCssApi extends PublicExtensionApi { - overridenByProjectConfigFile: BudPostCss[`overridenByProjectConfigFile`] - setPlugin: BudPostCss[`setPlugin`] + postcssOptions: BudPostCss[`postcssOptions`] + getPlugin: BudPostCss[`getPlugin`] getPluginPath: BudPostCss[`getPluginPath`] setPluginPath: BudPostCss[`setPluginPath`] getPluginOptions: BudPostCss[`getPluginOptions`] setPluginOptions: BudPostCss[`setPluginOptions`] - getPlugins: BudPostCss[`getPlugins`] + setPlugin: BudPostCss[`setPlugin`] setPlugins: BudPostCss[`setPlugins`] unsetPlugin: BudPostCss[`unsetPlugin`] - plugins: BudPostCss[`plugins`] + use: BudPostCss[`use`] getSyntax: BudPostCss[`getSyntax`] setSyntax: BudPostCss[`setSyntax`] getSourceMap: BudPostCss[`getSourceMap`] diff --git a/sources/@roots/bud-postcss/src/extension.test.ts b/sources/@roots/bud-postcss/test/extension.test.ts similarity index 77% rename from sources/@roots/bud-postcss/src/extension.test.ts rename to sources/@roots/bud-postcss/test/extension.test.ts index 90f09308bc..6caafb1d3e 100644 --- a/sources/@roots/bud-postcss/src/extension.test.ts +++ b/sources/@roots/bud-postcss/test/extension.test.ts @@ -1,7 +1,9 @@ -import {factory} from '@repo/test-kit/bud' +import {Bud, factory} from '@repo/test-kit/bud' import {describe, expect, it} from 'vitest' -import BudPostCss from './index.js' +import BudPostCss from '../src/index.js' + +const resetPlugins = (bud: Bud) => bud.postcss.set(`plugins`, {}) describe(`@roots/bud-postcss`, () => { it(`label`, async () => { @@ -11,42 +13,32 @@ describe(`@roots/bud-postcss`, () => { expect(bud.postcss.label).toBe(`@roots/bud-postcss`) }) - it(`getPlugins`, async () => { - const bud = await factory() - await bud.extensions.add(BudPostCss) - expect(bud.postcss.getPlugins()).toBe(bud.postcss.plugins) - }) - it(`setPlugins from obj`, async () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins({foo: [`bar`]}) - expect(bud.postcss.getPlugins()).toStrictEqual( - new Map([[`foo`, [`bar`]]]), - ) + expect(bud.postcss.get(`plugins.foo`)).toStrictEqual([`bar`]) }) it(`setPlugins from map`, async () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins(new Map([[`bang`, [`bop`]]])) - expect(bud.postcss.getPlugins()).toStrictEqual( - new Map([[`bang`, [`bop`]]]), - ) + expect(bud.postcss.get(`plugins.bang`)).toStrictEqual([`bop`]) }) it(`getPluginOptions`, async () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins(new Map([[`bang`, [`bop`]]])) @@ -54,23 +46,23 @@ describe(`@roots/bud-postcss`, () => { expect(options).toStrictEqual({}) }) - it(`setPluginOptions`, async () => { + it(`should have functioning setPluginOptions method`, async () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins(new Map([[`bang`, [`bop`]]])) bud.postcss.setPluginOptions(`bang`, {}) - expect(bud.postcss.plugins.get(`bang`)?.pop()).toStrictEqual({}) + expect(bud.postcss.get(`plugins.bang`)).toStrictEqual([`bop`, {}]) }) it(`setPluginOptions (callback)`, async () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins(new Map([[`bang`, [`bop`]]])) bud.postcss.setPluginOptions(`bang`, {foo: `bar`}) @@ -85,7 +77,7 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins(new Map([[`bang`, [`setPluginPath test`]]])) @@ -98,21 +90,19 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins(new Map([[`bang`, [`bop`]]])) bud.postcss.setPluginPath(`bang`, `newPath`) - expect(bud.postcss.plugins.get(`bang`)?.shift()).toStrictEqual( - `newPath`, - ) + expect(bud.postcss.get(`plugins.bang`)?.[0]).toStrictEqual(`newPath`) }) it(`unsetPlugin`, async () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugins( new Map([ @@ -126,7 +116,7 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) const returnValue = bud.postcss.setPlugins( new Map([ @@ -142,7 +132,7 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) const returnValue = bud.postcss.setPlugins( new Map([ @@ -158,11 +148,11 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) bud.postcss.setPlugin(`boop`) - expect(bud.postcss.plugins.get(`boop`)).toEqual( + expect(bud.postcss.get(`plugins.boop`)).toEqual( expect.arrayContaining([expect.stringContaining(`boop`)]), ) }) @@ -171,12 +161,12 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) const signifier = `postcss-preset-env` bud.postcss.setPlugin(`env`, signifier) - expect(bud.postcss.plugins.get(`env`)).toEqual( + expect(bud.postcss.get(`plugins.env`)).toEqual( expect.arrayContaining([expect.stringContaining(signifier)]), ) }) @@ -185,12 +175,12 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) const signifier = `postcss-preset-env` bud.postcss.setPlugin(`env`, [signifier, {option: `value`}]) - expect(bud.postcss.plugins.get(`env`)).toEqual( + expect(bud.postcss.get(`plugins.env`)).toEqual( expect.arrayContaining([ expect.stringContaining(signifier), expect.objectContaining({option: `value`}), @@ -203,7 +193,7 @@ describe(`@roots/bud-postcss`, () => { await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) try { expect(bud.postcss.getPluginOptions(`no-exist`)).toThrow() @@ -214,7 +204,7 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) expect(bud.build.loaders.postcss.getSrc()).toContain(`postcss-loader`) }) @@ -223,7 +213,7 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) expect(bud.build.items.postcss?.getLoader().getSrc()).toContain( `postcss-loader`, @@ -234,8 +224,19 @@ describe(`@roots/bud-postcss`, () => { const bud = await factory() await bud.extensions.add(BudPostCss) - bud.postcss.plugins.clear() + resetPlugins(bud) expect(bud.build.rules.css?.getUse()).toContain(`postcss`) }) + + it(`should set the order with bud.postcss.use`, async () => { + const bud = await factory() + await bud.extensions.add(BudPostCss) + resetPlugins(bud) + bud.postcss.use([`foo`, `bar`]) + expect(bud.postcss.get(`order`)).toStrictEqual([`foo`, `bar`]) + + bud.postcss.use(plugins => plugins.map(plugin => `${plugin}!`)) + expect(bud.postcss.get(`order`)).toStrictEqual([`foo!`, `bar!`]) + }) }) diff --git a/sources/@roots/bud-purgecss/src/api.ts b/sources/@roots/bud-purgecss/src/api.ts index ab69076870..9ecbe38c99 100644 --- a/sources/@roots/bud-purgecss/src/api.ts +++ b/sources/@roots/bud-purgecss/src/api.ts @@ -83,10 +83,9 @@ export interface Extractors { * ``` */ export const purgecss: purge = function (userOptions) { - this.postcss.setPlugin(`purgecss`, [ - `@fullhuman/postcss-purgecss`, - userOptions, - ]) + this.postcss + .setPlugin(`purgecss`, [`@fullhuman/postcss-purgecss`, userOptions]) + .use(plugins => [...plugins, `purgecss`]) return this } diff --git a/sources/@roots/bud-purgecss/test/extension.test.ts b/sources/@roots/bud-purgecss/test/extension.test.ts index 3f78b09fe9..7a4f1300cc 100644 --- a/sources/@roots/bud-purgecss/test/extension.test.ts +++ b/sources/@roots/bud-purgecss/test/extension.test.ts @@ -1,3 +1,5 @@ +import '../src/index.js' + import {Bud, factory} from '@repo/test-kit/bud' import postcss from '@roots/bud-postcss' import {beforeEach, describe, expect, it} from 'vitest' @@ -34,6 +36,8 @@ describe(`@roots/bud-purgecss`, () => { it(`should add plugin to the postcss plugins repository`, () => { purgecss.bind(bud)({content: [`**/*.html`]}) - expect(bud.postcss.plugins.has(`purgecss`)).toBe(true) + expect(bud.postcss.getPluginOptions(`purgecss`)).toStrictEqual({ + content: [`**/*.html`], + }) }) }) diff --git a/sources/@roots/bud-tailwindcss/src/extension/index.ts b/sources/@roots/bud-tailwindcss/src/extension/index.ts index 94dea1e37f..313acbb0fe 100644 --- a/sources/@roots/bud-tailwindcss/src/extension/index.ts +++ b/sources/@roots/bud-tailwindcss/src/extension/index.ts @@ -159,18 +159,20 @@ export class BudTailwindCss extends Extension { ) } - bud.postcss.setPlugins({ - nesting: await this.resolve( - join(`tailwindcss`, `nesting`, `index.js`), - import.meta.url, - ), - tailwindcss: [ + bud.postcss + .setPlugin( + `nesting`, + await this.resolve( + join(`tailwindcss`, `nesting`, `index.js`), + import.meta.url, + ), + ) + .setPlugin(`tailwindcss`, [ await this.resolve(`tailwindcss`, import.meta.url), this.file.module ?? this.file.path, - ], - }) - - this.logger.success(`postcss configured for tailwindcss`) + ]) + .use([`import`, `nesting`, `tailwindcss`, `env`]) + .logger.success(`postcss configured for tailwindcss`) /** * Add tailwind config to webpack cache dependencies @@ -247,7 +249,7 @@ export class BudTailwindCss extends Extension { } public override async buildBefore() { - if (this.app.postcss.overridenByProjectConfigFile) { + if (this.app.postcss.get(`postcssOptions.config`)) { this.logger.warn( `PostCSS configuration overridden by project config file.`, `@roots/bud-tailwindcss configuration will not apply.`, diff --git a/sources/@roots/bud-tailwindcss/test/extension/index.test.ts b/sources/@roots/bud-tailwindcss/test/extension/index.test.ts index 86de20bb7b..787c797e96 100644 --- a/sources/@roots/bud-tailwindcss/test/extension/index.test.ts +++ b/sources/@roots/bud-tailwindcss/test/extension/index.test.ts @@ -110,10 +110,10 @@ describe(`@roots/bud-tailwindcss extension`, () => { }) it(`should call postcss.setPlugins`, async () => { - const setPluginsSpy = vi.spyOn(bud.postcss, `setPlugins`) + const setSpy = vi.spyOn(bud.postcss, `set`) await extension.boot(bud) - expect(setPluginsSpy).toHaveBeenCalled() + expect(setSpy).toHaveBeenCalled() }) })