tailwindlabs / tailwindcss

A utility-first CSS framework for rapid UI development.
https://tailwindcss.com/
MIT License
83.12k stars 4.21k forks source link

Plugin config extensions aren't deepmerged. #15021

Open Hugos68 opened 3 hours ago

Hugos68 commented 3 hours ago

What version of Tailwind CSS are you using?

What build tool (or framework if it abstracts the build tool) are you using?

What version of Node.js are you using?

What browser are you using?

What operating system are you using?

Reproduction URL

Unsure how to reproduce in tailwind.play as it's a config issue and I can't access the post merge config.

Describe your issue

In the tailwind docs it states quote:

Plugins can merge their own set of configuration values into the user’s tailwind.config.js configuration by providing an object as the second argument to the plugin function.

So when doing the following:

import plugin from 'tailwindcss/plugin';

export default {
    content: ['one']
    plugins: [
       plugin(() => {}, { content: ['two'] })
    ]
}

I would expect the merged config to look like:

export default {
    content: ['one', 'two']
}

But the actual resulted config is:

export default {
    content: ['one']
}

This means because the content field is present in the config, the plugin's content field is completely ignored. This makes authoring plugins more painful as know I need to instruct users to add stuff to their content field manually instead of have my plugin do this for them.

Is this intended behaviour?

wongjn commented 3 hours ago

It is intended behavior. The plugin configuration logic follows that of presets:

The following options in tailwind.config.js simply replace the same option if present in a preset:

  • content
  • darkMode
  • prefix
  • important
  • variantOrder
  • separator
  • safelist

We can also see in the source code:

https://github.com/tailwindlabs/tailwindcss/blob/f65023efb97832660dc17cf954504f9f156047ba/src/util/resolveConfig.js#L255-L277

It explicitly merges theme, corePlugins and plugins, but the rest of the configuration keys are "collapsed", replacing each other (via the defaults() function).

Hugos68 commented 3 hours ago

Can/will this ever change? It makes authoring plugins in some scenario's a bit difficult. As I'm authoring a component library that utilises tailwind so the end user needs to configure their content path to include our package inside node_modules. It would be awesom if our already present tailwind plugin could handle this for them.

wongjn commented 3 hours ago

As a work-around, you could consider having consumers wrap their configuration object in a function you export:

import { tailwindConfigWrapperFunction } = 'my-library';

export default tailwindConfigWrapperFunction({
  // Consumer Tailwind configuration.
});

This would allow you to augment the configuration as desired.