-
-
Notifications
You must be signed in to change notification settings - Fork 780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(svelte-scoped)!: introduce@unocss/svelte-scoped
package
#2552
Conversation
✅ Deploy Preview for unocss ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
Thanks a lot for linking the issues. Looking forward to it! btw @jacob-8 we have an internal channel on Discord, you can join via chat.antfu.me, and let me know who you are so I can give you the role. |
@@ -0,0 +1,364 @@ | |||
/* Tailwind Reset */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we reuse @unocss/reset
and avoid duplication here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, though I need help to do that. SvelteKit provides no options to import styles in a specific location in the sense that we would normally place the resets at the top of some main.ts
like file. So I'm placing them at the start of the global css that will be injected into the head above SvelteKit styles.
In global.ts
in my vite plugin I've tried all these iterations with no success:
They all incur this error:
error when starting dev server:
C:\dev\unocss\packages\reset\tailwind.css:6
*,
^
SyntaxError: Unexpected token '*'
at Object.compileFunction (node:vm:360:18)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need it to be a string? Can we directly import '@unocss/reset/tailwind.css'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it needs to be a string that I can inject directly into the head along with preflights/safelist (in dev it's inlined, in build it's a turned into a separate hashed css file) because there is no place in SvelteKit into which we can directly import CSS such that it will come first. If user's manually import into it, say into their root +layout.svelte
file, the reset could end up being placed into the head
tag anywhere amongst other imported styles.
So it looks like the only way we can reuse @unocss/reset/tailwind.css
is if a Vite plugin can take a CSS file and give us a string to use in our function. I don't know how to do that. And the only other alternative is to just let users handle resets on their own, separate from Uno. I really, really like the convenience of Svelte Scoped easily providing a reset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also like the simplicity for adding a reset. Couldn't you just read the file from the filesystem to get the contents?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the css is always loaded and always before other css from child layouts afaik
EDIT: Not sure how it interacts with other layouts, but the +layout/+page styles ordering is not consistent. See https://stackblitz.com/edit/sveltejs-kit-template-default-ceqnem?file=src%2Froutes%2Fstyles.css,src%2Froutes%2F%2Blayout.svelte,src%2Froutes%2FHeader.svelte
In dev mode, loading from server has this order:
- root +layout.svelte style block: breaks things for us
- styles.css imported by root +layout.svelte (represents resets/preflights/safelist imported in the root layout)
- Header.svelte style block: this component is imported into root layout above global styles simulating a user error - the server handles this in our "desired" manner and imports after stylesheet imports
- +page.svelte style block: desired behavior
Then when client hydrates it has this order:
- +page.svelte style block: this breaks things for us
- Header.svelte style block: the user error of placing this above the stylesheet import now bites us
- styles.css
- +layout.svelte: now it's correct on client
In each case we want styles.css to come first. The client has an issue with styles placed in the +page.svelte style block and the server has an issue of placing +layout.svelte style block first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@antfu custom module resolution for injectReset
is implemented:
unocss/packages/svelte-scoped/src/vite/types.d.ts
Lines 9 to 24 in 915a68d
/** | |
* Inject reset to the beginning of the global stylesheet. | |
* | |
* You can pass one of the included resets from [@unocss/reset](https://unocss.dev/guide/style-reset): | |
* - `@unocss/reset/normalize.css` | |
* - `@unocss/reset/eric-meyer.css` | |
* - `@unocss/reset/sanitize/sanitize.css` | |
* - `@unocss/reset/tailwind.css` | |
* - `@unocss/reset/tailwind-compat.css` | |
* | |
* You can pass your own custom reset by passing the filepath relative to your project root as in `./src/reset.css` | |
* | |
* You can install a package then pass a path to the CSS file in your node_modules as in `@bob/my-tools/reset.css`. | |
* @default undefined | |
*/ | |
injectReset?: string |
unocss/packages/svelte-scoped/src/vite/getReset.ts
Lines 9 to 29 in 2b2096f
export function getReset(injectReset: string): string { | |
if (injectReset.startsWith('@unocss/reset')) | |
return readFileSync(resolve(_dirname, `../node_modules/${injectReset}`), 'utf-8') | |
if (injectReset.startsWith('.')) { | |
const resolved = resolve(process.cwd(), injectReset) | |
if (!isFile(resolved)) | |
throw new Error(`"${injectReset}" given as your injectReset value is not a valid file path relative to the root of your project, where your vite config file sits. To give an example, if you placed a reset.css in your src directory, "./src/reset.css" would work.`) | |
return readFileSync(resolved, 'utf-8') | |
} | |
if (injectReset.startsWith('/')) | |
throw new Error(`Your injectReset value: "${injectReset}" is not a valid file path. To give an example, if you placed a reset.css in your src directory, "./src/reset.css" would work.`) | |
const resolvedFromNodeModules = resolve(process.cwd(), 'node_modules', injectReset) | |
if (!isFile(resolvedFromNodeModules)) | |
throw new Error(`"${injectReset}" given as your injectReset value is not a valid file path relative to your project's node_modules folder. Can you confirm that you've installed "${injectReset}"?`) | |
return readFileSync(resolvedFromNodeModules, 'utf-8') | |
} |
If that looks good, this review note can be resolved.
@dominikg, please forgive me if I've come across as argumentative. I admire your contributions to Svelte (literally every time I use the Svelte inspector) and am just happy to be in conversation with you and others who make great tools.
The svelte-scoped mode as it's seen here is the result of me using various iterations of wind+svelte-scoping for almost 2 years. I started with https://github.com/windicss/svelte-windicss-preprocess, then added a preprocessor to run before that preprocessor to overcome some missing features I wanted that are available out of the box with a normal Tailwind setup. I still didn't have feature parity however, and when I came upon UnoCSS, it finally gave a platform where we could build something that had feature parity.
Due to the various complexities of what we are trying to accomplish, I struggle with how to properly explain how it works. I gave another attempt by adding a bit on how it works to the docs and hope that gives more clarity. If not, all I can say is that this is the best way to accomplish utility styles in a distributed fashion across Svelte components that I've used. I've also not seen any other solution that supports all the use cases. It's the only way I've found to be able to do extensive styling in a large and complex app without feeling any friction. It's a wonderful DX in my opinion, and I hope others will like it too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't worry, I like to argument a lot, and it seems you have put a lot of thought into it. I still believe there should be better, less duplicating ways to achieve unocss codesplitting (from what I understand is that you want to contain rarely used tokens to separate .css files that are only loaded on some routes. eg. some specific icons or these complex custom things that tend to creep into projects over time.
As long as there is a way to use unocss+svelte without this svelte-scoped mode and unocss sees a value in it, i'm ok with this feature.
For the technical aspects there might be some misconceptions going on either on my part or on yours. The way you are using :global to escape sveltes own scoping is not really recommended.
:global is meant as the occasional escape hatch and it is recommended to only use it with a prefix like div :global(.something)
so that it is still scoped to the component, but applies to anything inside.
So would there be a way where you use svelte's own scoping instead? This should also avoid the ordering issue you described (apart from users putting imports in the wrong order)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So would there be a way where you use svelte's own scoping instead? This should also avoid the ordering issue you described (apart from users putting imports in the wrong order)
Our PostCSS plugin will be doing exactly that. From my comment at #2530 (comment)
I'm working on some sort of scoped feature for our PostCSS plugin that can also be supported by the Vite plugin in the future. I have already created a POC locally. No transformation, no Vite plugin, vite-plugin-svelte does the scoping and hashing for us. It works great. I will make a proposal about it ASAP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have already created a POC locally. No transformation, no Vite plugin, vite-plugin-svelte does the scoping and hashing for us. It works great. I will make a proposal about it ASAP.
Thanks for adding in. I'm looking forward to it.
In your proposal, please address the ability to pass classes to children components for which I've found no solution in Svelte's default hashing. Here's the problem:
To allow for this usage you will need to detect when the class="..."
attribute is part of a Component as opposed to an element. Then you will need to generate the css for those classes separately and wrap them in :global()
to allow them to have effect when placed into the child component.
That will then lead to bugs in a situation where A passes classes to B that conflict with the classes C passes to D. In this scenario, if C loads later then B's intended behavior will be flipped:
A > B
.hidden .sm:block
C > D
.block .sm:hidden
The only 2 ways around this are to place all styles in a global stylesheet (what everyone does) or to compile the class names into unique names for each component so they don't conflict (what Svelte Scoped does).
So our choices are:
- Don't compile class names. Use Svelte's default hashing and adjust Uno's generated CSS to have :global() wrappers only around the parts of rule selectors which are added in generation (e.g.
[dir="rtl"]
). There is still a good amount of footwork required for this as seen in the apply-directives feature. This means cutting out the passing classes to children feature. - Do all of the above + compile class names just when they are passed to children to be able to keep the feature.
- Compile all class names and let them be global.
The first kills a feature. The latter two still require injecting anything that needs to come first, like resets, safelist, etc., into the head before sveltekit styles.
Some like this passing classes feature, and some don't like it, but for my usage Svelte Scoped does not have feature parity with regular global styles setups without it. Who knows, maybe I'm the odd duck though. 😳
…o svelte-scoped-packages
As |
@BlankParticle, yes, |
svelte-scoped
mode into its own Vite plugin and Svelte preprocessor@unocss/svelte-scoped
package
Merging this for now to move forward, thanks a lot for the great work! |
See discussion starting at #2530 (comment) for context.
Adds
@unocss/svelte-scoped
with two options:@unocss/svelte-scoped/vite
@unocss/svelte-scoped/preprocess
Status:
svelte-scoped
related issues as things got a little stale in the intermediary branch away from the project and now a return backapply
directives:global()
but not the original class name so that the Svelte compiler won't strip out the class it would otherwise see as unusedclass:mb-1={flag}
syntax.svelte-scoped
mode #2530 and integrate improvements, closes refactor(svelte-scoped)!: rewritesvelte-scoped
mode #2530class=""
bug, fixes Element with empty class attribute breaks UnoCSS classes in next element in SvelteKit scoped #2420svelte-scoped
mode with an info notice