diff --git a/docs/integrations/svelte-scoped.md b/docs/integrations/svelte-scoped.md index 5b2902b805..db7a3a2219 100644 --- a/docs/integrations/svelte-scoped.md +++ b/docs/integrations/svelte-scoped.md @@ -314,33 +314,25 @@ Setup your `uno.config.ts` file as described [below](#configuration). ### Preflights -When using the preprocessor you have the option to include preflights in your component by adding `uno:preflights` as a style attribute. +When using the preprocessor you have the option to include preflights in a component by adding `uno:preflights` as a style attribute. ```html ``` -Adding preflights into individual components is unnecessary if your classes do not depend on preflights or your built components are being consumed only in apps that already include preflights. +Any special preflights that start with a period, such as `.prose :where(a):not(:where(.not-prose, .not-prose *))`, will be wrapped with `:global()` to avoid being automatically stripped out by the Svelte compiler. + +*Adding preflights into individual components is unnecessary if your classes do not depend on preflights or your built components are being consumed only in apps that already include preflights.* ### Safelist -When using the preprocessor you have the option to include safelist classes in your component by adding `uno:safelist` as a style attribute. +When using the preprocessor you have the option to include safelist classes in a component by adding `uno:safelist` as a style attribute. ```html - + ``` -To avoid having the Svelte compiler then strip them out because they're not found in the component, you'll also need to add the `global` modifier which will require https://github.com/sveltejs/svelte-preprocess. It's probably easier to just use `--at-apply` instead: - -```svelte -
- - -``` +Your safelist styles will be wrapped with `:global()` to avoid being automatically stripped out by the Svelte compiler. ## Configuration @@ -368,16 +360,16 @@ Do to the nature of having a few necessary styles in a global stylesheet and eve | Preset | Supported | Notes | | --- | :-- | :-- | | [@unocss/preset-uno](https://unocss.dev/presets/uno), [@unocss/preset-mini](https://unocss.dev/presets/mini), [@unocss/preset-wind](https://unocss.dev/presets/wind), [@unocss/preset-icons](https://github.com/unocss/unocss/tree/main/packages/preset-icons), [@unocss/web-fonts](https://github.com/unocss/unocss/tree/main/packages/preset-icons) | ✅ | These and all community plugins, e.g. [unocss-preset-forms](https://github.com/Julien-R44/unocss-preset-forms), that only rely on rules/variants/preflights will work. | -| [@unocss/preset-typography](https://github.com/unocss/unocss/tree/main/packages/preset-typography) | ✅ | Using the `.prose` class adds a large amount of rulesets which Svelte Scoped will not properly surround with `:global()` wrappers so add the `prose` class to your safelist when using this preset. All other classes from this preset, e.g. `prose-pink`, can be component scoped. | +| [@unocss/preset-typography](https://github.com/unocss/unocss/tree/main/packages/preset-typography) | ✅ | Due to how this preset adds rulesets to your preflights you must add the `prose` class to your safelist when using this preset, otherwise the preflights will never be triggered. All other classes from this preset, e.g. `prose-pink`, can be component scoped. | | [@unocss/preset-rem-to-px](https://github.com/unocss/unocss/tree/main/packages/preset-rem-to-px) | ✅ | This and all presets like it that only modify style output will work. | | [@unocss/preset-attributify](https://github.com/unocss/unocss/tree/main/packages/preset-attributify) | - | Preset won't work. Instead use [unplugin-attributify-to-class](https://github.com/MellowCo/unplugin-attributify-to-class) Vite plugin (`attributifyToClass({ include: [/\.svelte$/]})`) before the Svelte Scoped Vite plugin | | [@unocss/preset-tagify](https://github.com/unocss/unocss/tree/main/packages/preset-tagify) | - | Presets that add custom extractors will not work. Create a preprocessor to convert `Hi` to `Hi`, then create a PR to add the link here. | -For other presets, if they don't rely on traditional `class="..."` usage you will need to first preprocess those class names into the `class="..."` attribute. If they add extremely complex styles like typography's `.prose` class then you may need to place the complex class names into your safelist. +For other presets, if they don't rely on traditional `class="..."` usage you will need to first preprocess those class names into the `class="..."` attribute. If they add presets like typography's `.prose` class then you will need to place the classes which trigger the preset additions into your safelist. -## Scoped utility classes unleashes creativity +## Scoped utility classes unleash creativity -Some advice on when you might want to use scoped styles: A global css file that includes everything is great for smaller apps, but there will come a point in a large project's life when every time you start to write a class like `.md:max-w-[50vw]` that you know is only going to be used once you start to cringe as you feel the size of your global style sheet getting larger and larger. This inhibits creativity. Sure, you could use `--at-apply: md:max-w-[50vw]` in the style block but that gets tedious and styles in context are so useful. Furthermore, if you would like to include a great variety of icons in your project, you will begin to feel the weight of adding them to the global stylesheet. When each component bears the weight of its own styles and icons you can continue to expand your project without having to analyze the cost benefit of each new addition. +Some advice on when you might want to use scoped styles: If you have come to the point in a large project's life when every time you use a class like `.md:max-w-[50vw]` that you know is only used once you cringe as you feel the size of your global style sheet getting larger and larger, then give this package a try. Hesitancy to use exactly the class you need inhibits creativity. Sure, you could use `--at-apply: md:max-w-[50vw]` in the style block but that gets tedious and styles in context are useful. Furthermore, if you would like to include a great variety of icons in your project, you will begin to feel the weight of adding them to the global stylesheet. When each component bears the weight of its own styles and icons you can continue to expand your project without having to analyze the cost benefit of each new addition. ## License diff --git a/packages/svelte-scoped/src/_preprocess/index.ts b/packages/svelte-scoped/src/_preprocess/index.ts index 15445054fb..1bd3600428 100644 --- a/packages/svelte-scoped/src/_preprocess/index.ts +++ b/packages/svelte-scoped/src/_preprocess/index.ts @@ -3,9 +3,10 @@ import { type UnoGenerator, type UserConfig, type UserConfigDefaults, createGene import presetUno from '@unocss/preset-uno' import { loadConfig } from '@unocss/config' import { transformClasses } from './transformClasses' -import { transformStyle } from './transformStyle' +import { checkForApply, transformStyle } from './transformStyle' import type { SvelteScopedContext, UnocssSveltePreprocessOptions } from './types' import { themeRE } from './transformTheme' +import { wrapSelectorsWithGlobal } from './transformClasses/wrapGlobal' export function UnocssSveltePreprocess(options: UnocssSveltePreprocessOptions = {}, unoContextFromVite?: SvelteScopedContext, isViteBuild?: () => boolean): PreprocessorGroup { if (!options.classPrefix) @@ -25,13 +26,20 @@ export function UnocssSveltePreprocess(options: UnocssSveltePreprocessOptions = }, style: async ({ content, attributes, filename }) => { - const addPreflights = !!attributes['uno:preflights'] - const addSafelist = !!attributes['uno:safelist'] + let addPreflights = !!attributes['uno:preflights'] + let addSafelist = !!attributes['uno:safelist'] - const checkForApply = options.applyVariables !== false - const hasThemeFn = content.match(themeRE) + if (unoContextFromVite && (addPreflights || addSafelist)) { + // Svelte 4 style preprocessors will be able to remove attributes after handling them, but for now we must ignore them when using the Vite plugin to avoid a SvelteKit app double-processing that which a component library already processed. + addPreflights = false + addSafelist = false + warnOnce('Notice for those transitioning to @unocss/svelte-scoped/vite: uno:preflights and uno:safelist are only for use in component libraries. Please see the documentation for how to add preflights and safelist into your head tag. If you are consuming a component library built by @unocss/svelte-scoped/preprocess, you can ignore this upgrade notice.') // remove notice in future + } + + const { hasApply, applyVariables } = checkForApply(content, options.applyVariables) + const hasThemeFn = !!content.match(themeRE) - const changeNeeded = addPreflights || addSafelist || checkForApply || hasThemeFn + const changeNeeded = addPreflights || addSafelist || hasApply || hasThemeFn if (!changeNeeded) return @@ -40,24 +48,23 @@ export function UnocssSveltePreprocess(options: UnocssSveltePreprocessOptions = let preflightsSafelistCss = '' if (addPreflights || addSafelist) { - if (unoContextFromVite) - warnOnce('Do not place preflights or safelist within an individual component as they already placed in your global styles injected into the head tag. These options are only for component libraries.') const { css } = await uno.generate([], { preflights: addPreflights, safelist: addSafelist, minify: true }) - preflightsSafelistCss = css + preflightsSafelistCss = wrapSelectorsWithGlobal(css) } - if (checkForApply || hasThemeFn) { + if (hasApply || hasThemeFn) { return await transformStyle({ content, - prepend: preflightsSafelistCss, uno, - applyVariables: options.applyVariables, filename, + prepend: preflightsSafelistCss, + applyVariables, + hasThemeFn, }) } if (preflightsSafelistCss) - return { code: preflightsSafelistCss } + return { code: preflightsSafelistCss + content } }, } } diff --git a/packages/svelte-scoped/src/_preprocess/transformClasses/wrapGlobal.ts b/packages/svelte-scoped/src/_preprocess/transformClasses/wrapGlobal.ts index 1a5a35fc12..570f97b830 100644 --- a/packages/svelte-scoped/src/_preprocess/transformClasses/wrapGlobal.ts +++ b/packages/svelte-scoped/src/_preprocess/transformClasses/wrapGlobal.ts @@ -17,7 +17,7 @@ if (import.meta.vitest) { const { describe, expect, it } = import.meta.vitest describe('wrapSelectorsWithGlobal', () => { - it('should wrap multiple selectors with :global()', () => { + it('wraps multiple selectors with :global()', () => { const css = '.my-class{color:red;}[dir="rtl"] .mb-1{margin-bottom:0.25em;}' const expected = ':global(.my-class){color:red;}:global([dir="rtl"] .mb-1){margin-bottom:0.25em;}' @@ -29,9 +29,7 @@ if (import.meta.vitest) { 0% { opacity: 0; } 100% { opacity: 1; } }` - const expected = css - - expect(wrapSelectorsWithGlobal(css)).toBe(expected) + expect(wrapSelectorsWithGlobal(css)).toBe(css) }) it('should not wrap @media selectors (selectors inside parenthesis)', () => { @@ -40,6 +38,13 @@ if (import.meta.vitest) { expect(wrapSelectorsWithGlobal(css)).toBe(expected) }) + + it('should not wrap selectors starting with * or ::', () => { + const cssWithAsterisk = '*,::before,::after { --un-rotate: 0;}' + expect(wrapSelectorsWithGlobal(cssWithAsterisk)).toBe(cssWithAsterisk) + const cssWithDoubleColon = '::backdrop { --un-rotate: 0;}' + expect(wrapSelectorsWithGlobal(cssWithDoubleColon)).toBe(cssWithDoubleColon) + }) }) } diff --git a/packages/svelte-scoped/src/_preprocess/transformStyle.ts b/packages/svelte-scoped/src/_preprocess/transformStyle.ts index d21321c498..874c0dadab 100644 --- a/packages/svelte-scoped/src/_preprocess/transformStyle.ts +++ b/packages/svelte-scoped/src/_preprocess/transformStyle.ts @@ -1,31 +1,33 @@ -import type { UnoGenerator } from '@unocss/core' -import { toArray } from '@unocss/core' +import { type UnoGenerator, toArray } from '@unocss/core' import type { Processed } from 'svelte/types/compiler/preprocess' import MagicString from 'magic-string' -import type { TransformApplyOptions } from './types' import { transformApply } from './transformApply' -import { themeRE, transformTheme } from './transformTheme' +import { transformTheme } from './transformTheme' +import type { TransformApplyOptions } from './types' const DEFAULT_APPLY_VARIABLES = ['--at-apply'] -export async function transformStyle({ content, uno, prepend, applyVariables, filename }: { +export function checkForApply(content: string, _applyVariables: TransformApplyOptions['applyVariables']) { + if (_applyVariables === false) + return { hasApply: false, applyVariables: [] } + const applyVariables = toArray(_applyVariables || DEFAULT_APPLY_VARIABLES) + return { + hasApply: content.includes('@apply') || applyVariables.some(v => content.includes(v)), + applyVariables, + } +} + +export async function transformStyle({ content, uno, prepend, filename, applyVariables, hasThemeFn }: { content: string uno: UnoGenerator - prepend?: string - applyVariables?: TransformApplyOptions['applyVariables'] filename?: string + prepend: string + applyVariables: string[] + hasThemeFn: boolean }): Promise { - applyVariables = toArray(applyVariables || DEFAULT_APPLY_VARIABLES) - const hasApply = content.includes('@apply') || applyVariables.some(v => content.includes(v)) - - const hasThemeFn = content.match(themeRE) - - if (!hasApply && !hasThemeFn) - return - const s = new MagicString(content) - if (hasApply) + if (applyVariables?.length) await transformApply({ s, uno, applyVariables }) if (hasThemeFn) diff --git a/packages/svelte-scoped/test/cases/preflights/Input.svelte b/packages/svelte-scoped/test/cases/preflights/Input.svelte index 682d6d1636..6446365796 100644 --- a/packages/svelte-scoped/test/cases/preflights/Input.svelte +++ b/packages/svelte-scoped/test/cases/preflights/Input.svelte @@ -2,6 +2,6 @@ diff --git a/packages/svelte-scoped/test/cases/prose/Input.svelte b/packages/svelte-scoped/test/cases/prose/Input.svelte new file mode 100644 index 0000000000..81ccf31296 --- /dev/null +++ b/packages/svelte-scoped/test/cases/prose/Input.svelte @@ -0,0 +1,3 @@ +
+ + \ No newline at end of file diff --git a/packages/svelte-scoped/test/cases/prose/OutputDev.svelte b/packages/svelte-scoped/test/cases/prose/OutputDev.svelte new file mode 100644 index 0000000000..ba707e2ebe --- /dev/null +++ b/packages/svelte-scoped/test/cases/prose/OutputDev.svelte @@ -0,0 +1,361 @@ +
+ + diff --git a/packages/svelte-scoped/test/cases/prose/OutputProd.svelte b/packages/svelte-scoped/test/cases/prose/OutputProd.svelte new file mode 100644 index 0000000000..ba707e2ebe --- /dev/null +++ b/packages/svelte-scoped/test/cases/prose/OutputProd.svelte @@ -0,0 +1,361 @@ +
+ + diff --git a/packages/svelte-scoped/test/index.test.ts b/packages/svelte-scoped/test/index.test.ts index 7abb5351ef..5784f20c93 100644 --- a/packages/svelte-scoped/test/index.test.ts +++ b/packages/svelte-scoped/test/index.test.ts @@ -6,6 +6,7 @@ import { format as prettier } from 'prettier' import prettierSvelte from 'prettier-plugin-svelte' import presetUno from '@unocss/preset-uno' import presetIcons from '@unocss/preset-icons' +import presetTypography from '@unocss/preset-typography' import UnocssSveltePreprocess from '../src/preprocess' import type { UnocssSveltePreprocessOptions } from '../src/preprocess' @@ -14,7 +15,7 @@ const defaultOptions: UnocssSveltePreprocessOptions = { // shortcuts: [ // { logo: 'i-logos:svelte-icon w-7em h-7em transform transition-300' }, // ], - safelist: ['mb-7px'], + safelist: ['mb-7px', 'uno-prose'], presets: [ presetUno(), presetIcons({ @@ -24,6 +25,9 @@ const defaultOptions: UnocssSveltePreprocessOptions = { 'vertical-align': 'middle', }, }), + presetTypography({ + selectorName: 'uno-prose', + }), ], }, }