From 3da9df0e63193a032b7f79ec0e59cfe94e5a2021 Mon Sep 17 00:00:00 2001 From: quotentiroler Date: Thu, 15 Oct 2026 01:50:10 +0200 Subject: [PATCH 1/2] Share Tailwind v3/v4 plugin code in shared_tailwind tailwind_v3.py and tailwind_v4.py duplicated compile_config, add_tailwind_to_css_file, _index_of_element_that_has, pre_compile, get_frontend_development_dependencies and four of the six Constants. These now live once in shared_tailwind: TailwindConstants holds the common constants, TailwindPlugin implements pre_compile and the dependency list, and each version plugin only supplies its root style, its postcss rewrite and its packages. The v4 plugin pinned tailwindcss and @tailwindcss/postcss as two separate literals that were always bumped together; both now derive from one version string. Generated output is byte-identical to main (config, root style with and without Radix, postcss and css rewrites, dependency lists for both versions). Module-level names stay importable from tailwind_v3 and tailwind_v4. Adds a test that each plugin wires its own root-style and postcss tasks and pins its packages to one version. --- .../news/+share-tailwind-plugin-code.misc.md | 1 + .../reflex_base/plugins/shared_tailwind.py | 155 +++++++++++++++++- .../src/reflex_base/plugins/tailwind_v3.py | 136 +++------------ .../src/reflex_base/plugins/tailwind_v4.py | 155 ++++-------------- tests/units/plugins/test_tailwind.py | 31 ++++ 5 files changed, 242 insertions(+), 236 deletions(-) create mode 100644 packages/reflex-base/news/+share-tailwind-plugin-code.misc.md diff --git a/packages/reflex-base/news/+share-tailwind-plugin-code.misc.md b/packages/reflex-base/news/+share-tailwind-plugin-code.misc.md new file mode 100644 index 00000000000..b476a963893 --- /dev/null +++ b/packages/reflex-base/news/+share-tailwind-plugin-code.misc.md @@ -0,0 +1 @@ +Share the Tailwind v3 and v4 plugin helpers in `shared_tailwind` and pin `@tailwindcss/postcss` from the same version as `tailwindcss`. diff --git a/packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py b/packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py index 7379273b164..ade32221577 100644 --- a/packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py +++ b/packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py @@ -1,17 +1,36 @@ -"""Tailwind CSS configuration types for Reflex plugins.""" +"""Tailwind CSS configuration types and helpers shared by the Tailwind plugins.""" import dataclasses import logging from collections.abc import Mapping from copy import deepcopy -from typing import Any, Literal, NotRequired, TypedDict +from pathlib import Path +from types import SimpleNamespace +from typing import Any, ClassVar, Literal, NotRequired, TypedDict from typing_extensions import Unpack +from reflex_base.constants.base import Dirs +from reflex_base.constants.compiler import Ext, PageNames + from .base import Plugin as PluginBase logger = logging.getLogger(__name__) + +class TailwindConstants(SimpleNamespace): + """Tailwind constants shared by every Tailwind version.""" + + # The Tailwind config. + CONFIG = "tailwind.config.js" + # Default Tailwind content paths + CONTENT = [f"./{Dirs.PAGES}/**/*.{{js,ts,jsx,tsx}}", "./utils/**/*.{js,ts,jsx,tsx}"] + # Relative tailwind style path to root stylesheet in Dirs.STYLES. + ROOT_STYLE_PATH = "./tailwind.css" + # The default tailwind css. + TAILWIND_CSS = "@import url('./tailwind.css');" + + TailwindPluginImport = TypedDict( "TailwindPluginImport", { @@ -170,6 +189,81 @@ def tailwind_config_js_template( """ +def compile_config(config: TailwindConfig): + """Compile the Tailwind config. + + Args: + config: The Tailwind config. + + Returns: + The compiled Tailwind config. + """ + return TailwindConstants.CONFIG, tailwind_config_js_template( + **config, + default_content=TailwindConstants.CONTENT, + ) + + +def compile_root_style_with( + template: str, radix_import: str, include_radix_themes: bool +) -> tuple[str, str]: + """Compile a Tailwind root style from a version-specific template. + + Args: + template: The root style template with a `{radix_import}` placeholder. + radix_import: The Radix stylesheet import line for this Tailwind version. + include_radix_themes: Whether to include the Radix stylesheet import. + + Returns: + The root style path and the compiled root style. + """ + return str(Path(Dirs.STYLES) / TailwindConstants.ROOT_STYLE_PATH), template.format( + radix_import=radix_import if include_radix_themes else "" + ) + + +def _index_of_element_that_has(haystack: list[str], needle: str) -> int | None: + return next( + (i for i, line in enumerate(haystack) if needle in line), + None, + ) + + +def add_tailwind_to_css_file( + css_file_content: str, *, include_radix_themes: bool = True +) -> str: + """Add tailwind to the css file. + + Args: + css_file_content: The content of the css file. + include_radix_themes: Whether the root stylesheet already imports Radix. + + Returns: + The modified css file content. + """ + from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET + + if TailwindConstants.TAILWIND_CSS.splitlines()[0] in css_file_content: + return css_file_content + if include_radix_themes and RADIX_THEMES_STYLESHEET in css_file_content: + return css_file_content.replace( + f"@import url('{RADIX_THEMES_STYLESHEET}');", + TailwindConstants.TAILWIND_CSS, + ) + + lines = css_file_content.splitlines() + insert_at = next( + ( + index + 1 + for index, line in enumerate(lines) + if "__reflex_style_reset.css" in line + ), + 1, + ) + lines.insert(insert_at, TailwindConstants.TAILWIND_CSS) + return "\n".join(lines) + + @dataclasses.dataclass class TailwindPlugin(PluginBase): """Plugin for Tailwind CSS.""" @@ -182,6 +276,32 @@ class TailwindPlugin(PluginBase): ) ) + _version_packages: ClassVar[tuple[str, ...]] = () + + @staticmethod + def _compile_root_style(include_radix_themes: bool = True) -> tuple[str, str]: + """Compile the root style for this Tailwind version. + + Args: + include_radix_themes: Whether to include the Radix stylesheet import. + + Raises: + NotImplementedError: Tailwind version plugins must override this. + """ + raise NotImplementedError + + @staticmethod + def _add_tailwind_to_postcss_config(postcss_file_content: str) -> str: + """Add this Tailwind version to the postcss config. + + Args: + postcss_file_content: The content of the postcss config file. + + Raises: + NotImplementedError: Tailwind version plugins must override this. + """ + raise NotImplementedError + def get_frontend_development_dependencies(self, **context) -> list[str]: """Get the packages required by the plugin. @@ -194,9 +314,34 @@ def get_frontend_development_dependencies(self, **context) -> list[str]: config = self.get_config() return [ - plugin if isinstance(plugin, str) else plugin.get("name") - for plugin in config.get("plugins", []) - ] + config.get("presets", []) + *( + plugin if isinstance(plugin, str) else plugin.get("name") + for plugin in config.get("plugins", []) + ), + *config.get("presets", []), + *self._version_packages, + ] + + def pre_compile(self, **context): + """Pre-compile the plugin. + + Args: + context: The context for the plugin. + """ + context["add_save_task"](compile_config, self.get_unversioned_config()) + include_radix_themes = context["radix_themes_plugin"].enabled + + context["add_save_task"](self._compile_root_style, include_radix_themes) + context["add_modify_task"]( + Dirs.POSTCSS_JS, self._add_tailwind_to_postcss_config + ) + context["add_modify_task"]( + str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)), + lambda content: add_tailwind_to_css_file( + content, + include_radix_themes=include_radix_themes, + ), + ) def get_config(self) -> TailwindConfig: """Get the Tailwind CSS configuration. diff --git a/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py b/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py index 66f575db5c2..04aa8ec17de 100644 --- a/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py +++ b/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py @@ -1,29 +1,32 @@ -"""Base class for all plugins.""" +"""Tailwind CSS v3 plugin.""" import dataclasses -from pathlib import Path -from types import SimpleNamespace +from typing import ClassVar -from reflex_base.constants.base import Dirs -from reflex_base.constants.compiler import Ext, PageNames from reflex_base.plugins.shared_tailwind import ( - TailwindConfig, + TailwindConstants, TailwindPlugin, - tailwind_config_js_template, + _index_of_element_that_has, + add_tailwind_to_css_file, + compile_config, + compile_root_style_with, ) +__all__ = [ + "Constants", + "TailwindV3Plugin", + "add_tailwind_to_css_file", + "add_tailwind_to_postcss_config", + "compile_config", + "compile_root_style", +] -class Constants(SimpleNamespace): + +class Constants(TailwindConstants): """Tailwind constants.""" # The Tailwindcss version VERSION = "tailwindcss@3.4.19" - # The Tailwind config. - CONFIG = "tailwind.config.js" - # Default Tailwind content paths - CONTENT = [f"./{Dirs.PAGES}/**/*.{{js,ts,jsx,tsx}}", "./utils/**/*.{js,ts,jsx,tsx}"] - # Relative tailwind style path to root stylesheet in Dirs.STYLES. - ROOT_STYLE_PATH = "./tailwind.css" # Content of the style content. ROOT_STYLE_CONTENT = """ @@ -35,24 +38,6 @@ class Constants(SimpleNamespace): @tailwind utilities; """ - # The default tailwind css. - TAILWIND_CSS = "@import url('./tailwind.css');" - - -def compile_config(config: TailwindConfig): - """Compile the Tailwind config. - - Args: - config: The Tailwind config. - - Returns: - The compiled Tailwind config. - """ - return Constants.CONFIG, tailwind_config_js_template( - **config, - default_content=Constants.CONTENT, - ) - def compile_root_style(include_radix_themes: bool = True): """Compile the Tailwind root style. @@ -65,19 +50,10 @@ def compile_root_style(include_radix_themes: bool = True): """ from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET - return str( - Path(Dirs.STYLES) / Constants.ROOT_STYLE_PATH - ), Constants.ROOT_STYLE_CONTENT.format( - radix_import=( - f"@import url('{RADIX_THEMES_STYLESHEET}');" if include_radix_themes else "" - ), - ) - - -def _index_of_element_that_has(haystack: list[str], needle: str) -> int | None: - return next( - (i for i, line in enumerate(haystack) if needle in line), - None, + return compile_root_style_with( + Constants.ROOT_STYLE_CONTENT, + f"@import url('{RADIX_THEMES_STYLESHEET}');", + include_radix_themes, ) @@ -117,74 +93,10 @@ def add_tailwind_to_postcss_config(postcss_file_content: str) -> str: return "\n".join(postcss_file_lines) -def add_tailwind_to_css_file( - css_file_content: str, *, include_radix_themes: bool = True -) -> str: - """Add tailwind to the css file. - - Args: - css_file_content: The content of the css file. - include_radix_themes: Whether the root stylesheet already imports Radix. - - Returns: - The modified css file content. - """ - from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET - - if Constants.TAILWIND_CSS.splitlines()[0] in css_file_content: - return css_file_content - if include_radix_themes and RADIX_THEMES_STYLESHEET in css_file_content: - return css_file_content.replace( - f"@import url('{RADIX_THEMES_STYLESHEET}');", - Constants.TAILWIND_CSS, - ) - - lines = css_file_content.splitlines() - insert_at = next( - ( - index + 1 - for index, line in enumerate(lines) - if "__reflex_style_reset.css" in line - ), - 1, - ) - lines.insert(insert_at, Constants.TAILWIND_CSS) - return "\n".join(lines) - - @dataclasses.dataclass class TailwindV3Plugin(TailwindPlugin): """Plugin for Tailwind CSS.""" - def get_frontend_development_dependencies(self, **context) -> list[str]: - """Get the packages required by the plugin. - - Args: - **context: The context for the plugin. - - Returns: - A list of packages required by the plugin. - """ - return [ - *super().get_frontend_development_dependencies(**context), - Constants.VERSION, - ] - - def pre_compile(self, **context): - """Pre-compile the plugin. - - Args: - context: The context for the plugin. - """ - context["add_save_task"](compile_config, self.get_unversioned_config()) - include_radix_themes = context["radix_themes_plugin"].enabled - - context["add_save_task"](compile_root_style, include_radix_themes) - context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config) - context["add_modify_task"]( - str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)), - lambda content: add_tailwind_to_css_file( - content, - include_radix_themes=include_radix_themes, - ), - ) + _version_packages: ClassVar[tuple[str, ...]] = (Constants.VERSION,) + _compile_root_style = staticmethod(compile_root_style) + _add_tailwind_to_postcss_config = staticmethod(add_tailwind_to_postcss_config) diff --git a/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py b/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py index 5f83d001d5c..c067ed03cd9 100644 --- a/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py +++ b/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py @@ -1,31 +1,39 @@ -"""Base class for all plugins.""" +"""Tailwind CSS v4 plugin.""" import dataclasses -from pathlib import Path -from types import SimpleNamespace +from typing import ClassVar -from reflex_base.constants.base import Dirs -from reflex_base.constants.compiler import Ext, PageNames from reflex_base.plugins.shared_tailwind import ( - TailwindConfig, + TailwindConstants, TailwindPlugin, - tailwind_config_js_template, + _index_of_element_that_has, + add_tailwind_to_css_file, + compile_config, + compile_root_style_with, ) +__all__ = [ + "Constants", + "TailwindV4Plugin", + "add_tailwind_to_css_file", + "add_tailwind_to_postcss_config", + "compile_config", + "compile_root_style", +] -class Constants(SimpleNamespace): +# Held at 4.3.0: 4.3.1+ reworked source-file scanning +# (tailwindlabs/tailwindcss#20214, #20217, #20203) and walks the +# git-ignored `.web` tree, which collapses the docs prod build at scale. +_TAILWIND_VERSION = "4.3.0" + + +class Constants(TailwindConstants): """Tailwind constants.""" - # The Tailwindcss version. Held at 4.3.0: 4.3.1+ reworked source-file - # scanning (tailwindlabs/tailwindcss#20214, #20217, #20203) and walks the - # git-ignored `.web` tree, which collapses the docs prod build at scale. - VERSION = "tailwindcss@4.3.0" - # The Tailwind config. - CONFIG = "tailwind.config.js" - # Default Tailwind content paths - CONTENT = [f"./{Dirs.PAGES}/**/*.{{js,ts,jsx,tsx}}", "./utils/**/*.{js,ts,jsx,tsx}"] - # Relative tailwind style path to root stylesheet in Dirs.STYLES. - ROOT_STYLE_PATH = "./tailwind.css" + # The Tailwindcss version. + VERSION = f"tailwindcss@{_TAILWIND_VERSION}" + # The Tailwind PostCSS plugin, released in lockstep with Tailwind. + POSTCSS_PLUGIN = f"@tailwindcss/postcss@{_TAILWIND_VERSION}" # Content of the style content. ROOT_STYLE_CONTENT = """@layer theme, base, components, utilities; @@ -35,24 +43,6 @@ class Constants(SimpleNamespace): @config "../tailwind.config.js"; """ - # The default tailwind css. - TAILWIND_CSS = "@import url('./tailwind.css');" - - -def compile_config(config: TailwindConfig): - """Compile the Tailwind config. - - Args: - config: The Tailwind config. - - Returns: - The compiled Tailwind config. - """ - return Constants.CONFIG, tailwind_config_js_template( - **config, - default_content=Constants.CONTENT, - ) - def compile_root_style(include_radix_themes: bool = True): """Compile the Tailwind root style. @@ -65,21 +55,10 @@ def compile_root_style(include_radix_themes: bool = True): """ from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET - return str( - Path(Dirs.STYLES) / Constants.ROOT_STYLE_PATH - ), Constants.ROOT_STYLE_CONTENT.format( - radix_import=( - f'@import "{RADIX_THEMES_STYLESHEET}" layer(components);\n' - if include_radix_themes - else "" - ), - ) - - -def _index_of_element_that_has(haystack: list[str], needle: str) -> int | None: - return next( - (i for i, line in enumerate(haystack) if needle in line), - None, + return compile_root_style_with( + Constants.ROOT_STYLE_CONTENT, + f'@import "{RADIX_THEMES_STYLESHEET}" layer(components);\n', + include_radix_themes, ) @@ -123,75 +102,13 @@ def add_tailwind_to_postcss_config(postcss_file_content: str) -> str: return "\n".join(postcss_file_lines) -def add_tailwind_to_css_file( - css_file_content: str, *, include_radix_themes: bool = True -) -> str: - """Add tailwind to the css file. - - Args: - css_file_content: The content of the css file. - include_radix_themes: Whether the root stylesheet already imports Radix. - - Returns: - The modified css file content. - """ - from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET - - if Constants.TAILWIND_CSS.splitlines()[0] in css_file_content: - return css_file_content - if include_radix_themes and RADIX_THEMES_STYLESHEET in css_file_content: - return css_file_content.replace( - f"@import url('{RADIX_THEMES_STYLESHEET}');", - Constants.TAILWIND_CSS, - ) - - lines = css_file_content.splitlines() - insert_at = next( - ( - index + 1 - for index, line in enumerate(lines) - if "__reflex_style_reset.css" in line - ), - 1, - ) - lines.insert(insert_at, Constants.TAILWIND_CSS) - return "\n".join(lines) - - @dataclasses.dataclass class TailwindV4Plugin(TailwindPlugin): """Plugin for Tailwind CSS.""" - def get_frontend_development_dependencies(self, **context) -> list[str]: - """Get the packages required by the plugin. - - Args: - **context: The context for the plugin. - - Returns: - A list of packages required by the plugin. - """ - return [ - *super().get_frontend_development_dependencies(**context), - Constants.VERSION, - "@tailwindcss/postcss@4.3.0", - ] - - def pre_compile(self, **context): - """Pre-compile the plugin. - - Args: - context: The context for the plugin. - """ - context["add_save_task"](compile_config, self.get_unversioned_config()) - include_radix_themes = context["radix_themes_plugin"].enabled - - context["add_save_task"](compile_root_style, include_radix_themes) - context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config) - context["add_modify_task"]( - str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)), - lambda content: add_tailwind_to_css_file( - content, - include_radix_themes=include_radix_themes, - ), - ) + _version_packages: ClassVar[tuple[str, ...]] = ( + Constants.VERSION, + Constants.POSTCSS_PLUGIN, + ) + _compile_root_style = staticmethod(compile_root_style) + _add_tailwind_to_postcss_config = staticmethod(add_tailwind_to_postcss_config) diff --git a/tests/units/plugins/test_tailwind.py b/tests/units/plugins/test_tailwind.py index 3f40b960172..cbdae590a10 100644 --- a/tests/units/plugins/test_tailwind.py +++ b/tests/units/plugins/test_tailwind.py @@ -1,4 +1,5 @@ from pathlib import Path +from types import SimpleNamespace import pytest from reflex_base.plugins import tailwind_v3, tailwind_v4 @@ -39,3 +40,33 @@ def test_v3_compile_root_style_keeps_expected_output_path(): output_path, _ = tailwind_v3.compile_root_style(include_radix_themes=False) assert output_path == str(Path("styles") / "tailwind.css") + + +@pytest.mark.parametrize( + ("plugin", "module", "packages"), + [ + (tailwind_v3.TailwindV3Plugin, tailwind_v3, ["tailwindcss@"]), + ( + tailwind_v4.TailwindV4Plugin, + tailwind_v4, + ["tailwindcss@", "@tailwindcss/postcss@"], + ), + ], +) +def test_plugin_wires_version_specific_tasks(plugin, module, packages): + """Each Tailwind plugin should register its own root style and postcss tasks.""" + save_tasks, modify_tasks = [], [] + instance = plugin() + instance.pre_compile( + add_save_task=lambda fn, *args: save_tasks.append(fn), + add_modify_task=lambda path, fn: modify_tasks.append(fn), + radix_themes_plugin=SimpleNamespace(enabled=False), + ) + + assert save_tasks == [module.compile_config, module.compile_root_style] + assert modify_tasks[0] is module.add_tailwind_to_postcss_config + version_packages = instance.get_frontend_development_dependencies()[ + -len(packages) : + ] + assert [p.rsplit("@", 1)[0] + "@" for p in version_packages] == packages + assert len({p.rsplit("@", 1)[1] for p in version_packages}) == 1 From 33d95fd840382f8d951c1c9310719f01848d422a Mon Sep 17 00:00:00 2001 From: quotentiroler Date: Thu, 15 Oct 2026 10:36:09 +0200 Subject: [PATCH 2/2] Keep TailwindConfig, TailwindPlugin and the template importable from version modules Before the refactor these names were imported into tailwind_v3 and tailwind_v4, so `from reflex_base.plugins.tailwind_v3 import TailwindConfig` worked. Re-import them and list them in __all__, with a test that pins the public names of both modules. --- .../src/reflex_base/plugins/tailwind_v3.py | 5 +++++ .../src/reflex_base/plugins/tailwind_v4.py | 5 +++++ tests/units/plugins/test_tailwind.py | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py b/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py index 04aa8ec17de..f59a0183de6 100644 --- a/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py +++ b/packages/reflex-base/src/reflex_base/plugins/tailwind_v3.py @@ -4,21 +4,26 @@ from typing import ClassVar from reflex_base.plugins.shared_tailwind import ( + TailwindConfig, TailwindConstants, TailwindPlugin, _index_of_element_that_has, add_tailwind_to_css_file, compile_config, compile_root_style_with, + tailwind_config_js_template, ) __all__ = [ "Constants", + "TailwindConfig", + "TailwindPlugin", "TailwindV3Plugin", "add_tailwind_to_css_file", "add_tailwind_to_postcss_config", "compile_config", "compile_root_style", + "tailwind_config_js_template", ] diff --git a/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py b/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py index c067ed03cd9..546961d0c1c 100644 --- a/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py +++ b/packages/reflex-base/src/reflex_base/plugins/tailwind_v4.py @@ -4,21 +4,26 @@ from typing import ClassVar from reflex_base.plugins.shared_tailwind import ( + TailwindConfig, TailwindConstants, TailwindPlugin, _index_of_element_that_has, add_tailwind_to_css_file, compile_config, compile_root_style_with, + tailwind_config_js_template, ) __all__ = [ "Constants", + "TailwindConfig", + "TailwindPlugin", "TailwindV4Plugin", "add_tailwind_to_css_file", "add_tailwind_to_postcss_config", "compile_config", "compile_root_style", + "tailwind_config_js_template", ] # Held at 4.3.0: 4.3.1+ reworked source-file scanning diff --git a/tests/units/plugins/test_tailwind.py b/tests/units/plugins/test_tailwind.py index cbdae590a10..d7ce6ade1a3 100644 --- a/tests/units/plugins/test_tailwind.py +++ b/tests/units/plugins/test_tailwind.py @@ -70,3 +70,23 @@ def test_plugin_wires_version_specific_tasks(plugin, module, packages): ] assert [p.rsplit("@", 1)[0] + "@" for p in version_packages] == packages assert len({p.rsplit("@", 1)[1] for p in version_packages}) == 1 + + +@pytest.mark.parametrize("module", [tailwind_v3, tailwind_v4]) +@pytest.mark.parametrize( + "name", + [ + "Constants", + "TailwindConfig", + "TailwindPlugin", + "add_tailwind_to_css_file", + "add_tailwind_to_postcss_config", + "compile_config", + "compile_root_style", + "tailwind_config_js_template", + ], +) +def test_version_modules_keep_their_public_names(module, name): + """Names importable from the version modules before sharing stay importable.""" + assert name in module.__all__ + assert getattr(module, name) is not None