From 5179c8388530908cf594dce938f68a69b8362bed Mon Sep 17 00:00:00 2001 From: jacob-8 Date: Thu, 1 Jun 2023 11:23:15 +0800 Subject: [PATCH 1/3] fix: global wrap needed preflights and safelist classes when used in component libraries --- docs/integrations/svelte-scoped.md | 30 +- .../svelte-scoped/src/preprocess/index.ts | 20 +- .../preprocess/transformClasses/wrapGlobal.ts | 13 +- .../src/preprocess/transformStyle.ts | 34 +- .../test/cases/preflights/Input.svelte | 2 +- .../test/cases/prose/Input.svelte | 3 + .../test/cases/prose/OutputDev.svelte | 361 ++++++++++++++++++ .../test/cases/prose/OutputProd.svelte | 361 ++++++++++++++++++ packages/svelte-scoped/test/index.test.ts | 6 +- 9 files changed, 780 insertions(+), 50 deletions(-) create mode 100644 packages/svelte-scoped/test/cases/prose/Input.svelte create mode 100644 packages/svelte-scoped/test/cases/prose/OutputDev.svelte create mode 100644 packages/svelte-scoped/test/cases/prose/OutputProd.svelte 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 e3b1fcb663..0895fb06e3 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 * from './types.d.js' @@ -30,10 +31,10 @@ export default function UnocssSveltePreprocess(options: UnocssSveltePreprocessOp const addPreflights = !!attributes['uno:preflights'] const addSafelist = !!attributes['uno:safelist'] - const checkForApply = options.applyVariables !== false - const hasThemeFn = content.match(themeRE) + 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 @@ -45,21 +46,22 @@ export default function UnocssSveltePreprocess(options: UnocssSveltePreprocessOp 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 19deb7a1bd..0389c6ac81 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/types' @@ -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', + }), ], }, } From 3ecdfbdae37be1206d1bb034318dbc82a3f05e93 Mon Sep 17 00:00:00 2001 From: jacob-8 Date: Thu, 1 Jun 2023 12:18:39 +0800 Subject: [PATCH 2/3] fix: don't apply uno:preflight/uno:safelist in Vite context --- packages/svelte-scoped/src/preprocess/index.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/svelte-scoped/src/preprocess/index.ts b/packages/svelte-scoped/src/preprocess/index.ts index 0895fb06e3..7d64b3b3ad 100644 --- a/packages/svelte-scoped/src/preprocess/index.ts +++ b/packages/svelte-scoped/src/preprocess/index.ts @@ -28,8 +28,15 @@ export default function UnocssSveltePreprocess(options: UnocssSveltePreprocessOp }, 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'] + + if (unoContextFromVite) { + // 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) @@ -43,8 +50,6 @@ export default function UnocssSveltePreprocess(options: UnocssSveltePreprocessOp 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 = wrapSelectorsWithGlobal(css) } From f7f133a1dda76852a469ab2f06549f29fe5a68b2 Mon Sep 17 00:00:00 2001 From: jacob-8 Date: Thu, 1 Jun 2023 12:20:08 +0800 Subject: [PATCH 3/3] Only show warning when necessary --- packages/svelte-scoped/src/preprocess/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte-scoped/src/preprocess/index.ts b/packages/svelte-scoped/src/preprocess/index.ts index 7d64b3b3ad..a69f53dc41 100644 --- a/packages/svelte-scoped/src/preprocess/index.ts +++ b/packages/svelte-scoped/src/preprocess/index.ts @@ -31,7 +31,7 @@ export default function UnocssSveltePreprocess(options: UnocssSveltePreprocessOp let addPreflights = !!attributes['uno:preflights'] let addSafelist = !!attributes['uno:safelist'] - if (unoContextFromVite) { + 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