From 9ab47329b8eb9b04519adbb7ba4890d1c2008cde Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 12 Aug 2024 14:44:44 -0400 Subject: [PATCH] Add support for `addBase` in plugins (#14172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds support for `addBase` in JS plugins which adds styles into the CSS base layer using `@layer base`. This exists for backwards compatibility with v3 but is not something we will encourage people to use going forward — in v4 it's better to just write these styles in a CSS file. In v3, `@layer base` was something we compiled away and was only used for determining where to add some styles in the final CSS, but in v4 we are using native CSS layers. This means that `addBase` in v4 expects you to have a _real_ `@layer base` in your final CSS, which you will have as long as you are using `@import "tailwindcss"` to add Tailwind to your project. Now something like this works: ```js function ({ addBase }) { addBase({ 'h1': { fontSize: '2em' }, 'h2': { fontSize: '1.5em' }, }) } ``` Which will emit the following CSS: ```css @layer base { h1 { font-size: 2em; } h2 { font-size: 1.5em; } } ``` The only limitation compared to v3 is that there is no way for you to wrap these styles in another custom layer. In v3 you could do this: ```css @layer my-base { @tailwind base; } ``` …and then anything you added with `addBase` would end up exactly where `@tailwind base` was in your source CSS. But in v4 there is no `@tailwind base`, so there's no way to wrap these styles in `@layer my-base` like in the example above. All base styles added by plugins are simply appended to the end of the stylesheet but wrapped in `@layer base` so they behave as if they are co-located with other base styles. Odds of this impacting anyone are extremely low, but if it proves to be an actual issue I think we could output these styles at the location of an optional `@tailwind base` rule if we detect it exists. --- CHANGELOG.md | 4 ++- packages/tailwindcss/src/index.test.ts | 40 ++++++++++++++++++++++++++ packages/tailwindcss/src/index.ts | 2 +- packages/tailwindcss/src/plugin-api.ts | 9 ++++-- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2afe7bcf2..50ab5fc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Added + +- Add support for `addBase` plugins using the `@plugin` directive ([#14172](https://github.com/tailwindlabs/tailwindcss/pull/14172)) ## [4.0.0-alpha.19] - 2024-08-09 diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index fd855fffb..c92d6c2d2 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -2074,3 +2074,43 @@ describe('@variant', () => { `) }) }) + +test('addBase', async () => { + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer base, utilities; + @layer utilities { + @tailwind utilities; + } + `, + + { + loadPlugin: async () => { + return ({ addBase }) => { + addBase({ + body: { + 'font-feature-settings': '"tnum"', + }, + }) + } + }, + }, + ) + + let compiled = build(['underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer base { + body { + font-feature-settings: "tnum"; + } + } + + @layer utilities { + .underline { + text-decoration-line: underline; + } + }" + `) +}) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 803f0c2be..1a9f1feb5 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -281,7 +281,7 @@ async function parseCss(css: string, { loadPlugin = throwOnPlugin }: CompileOpti customUtility(designSystem) } - let pluginApi = buildPluginApi(designSystem) + let pluginApi = buildPluginApi(designSystem, ast) await Promise.all(pluginLoaders.map((loader) => loader.then((plugin) => plugin(pluginApi)))) diff --git a/packages/tailwindcss/src/plugin-api.ts b/packages/tailwindcss/src/plugin-api.ts index 66e07024f..6e9230af5 100644 --- a/packages/tailwindcss/src/plugin-api.ts +++ b/packages/tailwindcss/src/plugin-api.ts @@ -1,10 +1,11 @@ import { substituteAtApply } from './apply' -import { objectToAst, rule, type CssInJs } from './ast' +import { objectToAst, rule, type AstNode, type CssInJs } from './ast' import type { DesignSystem } from './design-system' import { withAlpha, withNegative } from './utilities' import { inferDataType } from './utils/infer-data-type' export type PluginAPI = { + addBase(base: CssInJs): void addVariant(name: string, variant: string | string[] | CssInJs): void addUtilities(utilities: Record, options?: {}): void matchUtilities( @@ -20,8 +21,12 @@ export type PluginAPI = { const IS_VALID_UTILITY_NAME = /^[a-z][a-zA-Z0-9/%._-]*$/ -export function buildPluginApi(designSystem: DesignSystem): PluginAPI { +export function buildPluginApi(designSystem: DesignSystem, ast: AstNode[]): PluginAPI { return { + addBase(css) { + ast.push(rule('@layer base', objectToAst(css))) + }, + addVariant(name, variant) { // Single selector if (typeof variant === 'string') {