Skip to content
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(vite)!: svelte-scoped automatic compilation #1692

Merged
merged 65 commits into from Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
12f991f
update sveltekit example to @sveltejs/kit@1.0.0-next.506 (release can…
jacob-8 Oct 1, 2022
9515eeb
add sveltekit-rc readme
jacob-8 Oct 1, 2022
88a830b
remove unused sveltekit demo packages
jacob-8 Oct 1, 2022
f590538
remove -rc suffix to preserve sveltekit example links
jacob-8 Oct 1, 2022
bc48c5d
remove unused code
jacob-8 Oct 1, 2022
139a30a
add svelte-scoped sveltekit example
jacob-8 Oct 1, 2022
e0718dd
fix svelte-scoped breaking svelteExtractor because it doesn't pass on…
jacob-8 Oct 1, 2022
88861b4
make it possible to place preflights and safelist exactly where desired
jacob-8 Oct 1, 2022
41de467
separate logo class from logo variable
jacob-8 Oct 1, 2022
9f3327f
remove unused shortcuts
jacob-8 Oct 1, 2022
e6ffd03
fix: enable using svelte-scoped mode with svelteExtractor
jacob-8 Oct 1, 2022
68033cb
docs: add instructions on how to use uno:safelist uno:preflights in s…
jacob-8 Oct 1, 2022
cc43aad
illustrate that transformerCompileClass doesn't work in svelte-scoped…
jacob-8 Oct 1, 2022
0c868cd
add empty unocss.config.ts to get UnoCss VSCode extension to work
jacob-8 Oct 3, 2022
59bc312
adding blank unocss.config.ts to other sveltekit example to enable VS…
jacob-8 Oct 3, 2022
d229e64
Merge branch 'main' into scoped-compilation
jacob-8 Oct 3, 2022
32b05e1
add basic svelte-scoped-compiled plugin shell before adding tests
jacob-8 Oct 3, 2022
eb73c0d
add initial test
jacob-8 Oct 3, 2022
25540ce
chore: update deps
antfu Oct 3, 2022
07d0661
generate once per component, support class: syntax
jacob-8 Oct 3, 2022
0104a9d
chore: lint
jacob-8 Oct 3, 2022
4e81ebf
Merge branch 'main' into scoped-compilation
jacob-8 Oct 3, 2022
fdf6657
update mode target to svelte-scoped-compiled
jacob-8 Oct 3, 2022
4751a2a
don't add classname if all classes are unknown
jacob-8 Oct 4, 2022
29f3a33
chore: cleanup, add notes for next steps
jacob-8 Oct 4, 2022
0626d97
Merge branch 'scoped-compilation' of https://github.com/jacob-8/unocs…
jacob-8 Oct 4, 2022
caf1be5
prep for adding :global() around generated class names
jacob-8 Oct 6, 2022
f9a95e8
add :global() around selectors
jacob-8 Oct 6, 2022
353f9de
:global() does not mess with @media queries
jacob-8 Oct 6, 2022
c7ec15b
add logo to tests
jacob-8 Oct 7, 2022
9fbe51a
does not erroneously place global around animate-bounce keyframe digits
jacob-8 Oct 7, 2022
2453b3a
handles class directive shorthand syntax
jacob-8 Oct 7, 2022
ff0e539
simplify transformSFC function to just accepting UnoGenerator and not…
jacob-8 Oct 7, 2022
395a3d4
add --at-apply and more complex usages to sveltekit-scoped example
jacob-8 Oct 7, 2022
d4931bc
export functions from svelte-scoped-compiled
jacob-8 Oct 7, 2022
e744405
update sveltekit-scoped example to more clearly show failings of prev…
jacob-8 Oct 7, 2022
4b7f24b
chore: clean up test titles, order, and add everything example
jacob-8 Oct 7, 2022
7bf79c0
handle backticks and single quotes
jacob-8 Oct 7, 2022
103d12b
refactor to prepare for handle classes inside inline conditionals
jacob-8 Oct 7, 2022
f9dd8c6
feat: handles classes in inline conditionals
jacob-8 Oct 7, 2022
e889c77
chore: clean up comments
jacob-8 Oct 7, 2022
e76ddb4
update svelte-scoped to refer to be new version of plugin
jacob-8 Oct 9, 2022
2d4c48f
refactor and reorganize svelte-scoped transformSFC into multiple parts
jacob-8 Oct 9, 2022
036e823
Merge branch 'main' into scoped-compilation
jacob-8 Oct 9, 2022
83cea3a
chore: update docs
jacob-8 Oct 9, 2022
0ac7ec1
Merge branch 'scoped-compilation' of https://github.com/jacob-8/unocs…
jacob-8 Oct 9, 2022
d43ed7b
add sourcemap handling to transformSvelteSFC
jacob-8 Oct 10, 2022
ea5e4a7
chore: lint fix
jacob-8 Oct 10, 2022
3202422
fix: import type SourceMap from @unocss/core
jacob-8 Oct 10, 2022
0979b01
fix build error by not importing SourceMap type from Rollup
jacob-8 Oct 10, 2022
56f86a4
remove unneeded package because @unocss/transformer-directives is alr…
jacob-8 Oct 10, 2022
5fd6585
add example vite config to svelte-scoped example
jacob-8 Oct 10, 2022
350e910
make hashes unique to each file
jacob-8 Oct 13, 2022
f901034
dont combine class names on dev for easier use of dev tools toggle de…
jacob-8 Oct 18, 2022
799fc41
fix: dev, only hash but not combine mode, handles classes that fail w…
jacob-8 Oct 18, 2022
aa0aaf6
docs: add prose and reset to svelte-scoped example
jacob-8 Oct 20, 2022
b5363a7
move dev hash to back half
jacob-8 Oct 20, 2022
20e97ef
fix: does not change shortcut names
jacob-8 Oct 20, 2022
85e95bb
docs: add complete svelte-scoped usage instructions to example projec…
jacob-8 Oct 21, 2022
562a925
docs: update sveltekit-scoped example
jacob-8 Oct 21, 2022
3879c09
docs: update sveltekit-scoped prose block to not clash with dark mode
jacob-8 Oct 21, 2022
c536da8
chore: format
antfu Oct 25, 2022
f495841
Merge branch 'main' into scoped-compilation
antfu Oct 25, 2022
965b70f
chore: typecheck
antfu Oct 25, 2022
3d828a0
Merge branch 'main' into scoped-compilation
antfu Oct 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
241 changes: 233 additions & 8 deletions examples/sveltekit-scoped/README.md
@@ -1,12 +1,237 @@
# SvelteKit + UnoCSS Vite Plugin (svelte-scoped mode)
# SvelteKit + UnoCSS Vite Plugin (svelte-scoped)

- Works with @sveltejs/kit@1.0.0-next.506 (release candidate) and @sveltejs/adapter-auto@1.0.0-next.80
- Uses PresetUno and PresetIcons
- Uses `svelte-scoped` mode
- Uses `extractorSvelte` to be able to use `class:red-bg-200={true}` in components
## Why?

## Notes
### Scoping utility classes by component unleashes creativity

A global css file that only includes used utilities is great for small and medium 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 `@apply md:max-w-[50vw]` in the style block but that gets tedious. 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 building an evergrowing global stylesheet.

Another benefit is that if used to build a component library, your library won't need to match the particular Uno, Windi, Tailwind setup/version of your sites. This allows for easier of upgrades of all parts of your ecosystem, because they work well together but aren't dependent on each other. You also won't need to include a global stylesheet alongside your components for them to be used properly. You only need to pay attention to global theme variables and style resets.

### Completely isolated styles are impractical

There is a problem with purely isolated styles though. Many styles are dependent on elements and styles set in a parent or child component, such as `dark:`, `rtl:`, and `.space-x-1`. The issue of how to pass styles down to children components has often come up in Svelte chat threads. Fortunately `svelte-scoped` mode solves all of these problems as each utility class (or set of classes) is scoped based on filename + class name(s) hashes and made global. Because they are global they will have influence everywhere and because their names are unique they will conflict nowhere.

## Usage

Set up using `mode: 'svelte-scoped'` as described in the [Svelte/SvelteKit scoped section](/packages/vite/README.md#sveltesveltekit-scoped-mode) of the [Vite instructions](/packages/vite/README.md).

### Preflights & Safelist

- To use preflights add `<style uno:preflights global></style>` to your root `+layout.svelte`
- To use preflights add `<style uno:preflights global></style>` to your root `+layout.svelte` (some classes depend on these, like `.shadow`)
- To use safelist add `<style uno:safelist global></style>` to your root `+layout.svelte`
- Or to use both, add `<style uno:preflights uno:safelist global></style>` to your root `+layout.svelte` as demoed in this example repo. If you only want them to apply to 1 component just add them to that component's `style` tag and don't add `global`.
- Or to use both, add `<style uno:preflights uno:safelist global></style>` to your root `+layout.svelte` or a component imported there as demoed in this example repo. If you only want them to apply to 1 component just add them to that component's `style` tag and don't add `global`.

### Resets

Import reset stylesheets and anything else that you want utility classes to override in the head of `app.html` file before `%sveltekit.head%`. At present, placing them in your root layout file won't guarantee they are loaded in before specific component styles.

### Parent dependent classes

```svelte
<div class="ltr:left-0 rtl:right-0"></div>
```

turns into:

```svelte
<div class="uno-3hashz"></div>

<style>
:global([dir="ltr"] .uno-3hashz) {
left: 0rem;
}
:global([dir="rtl"] .uno-3hashz) {
right: 0rem;
}
</style>
```

### Children affecting classes

If an element in your component wants to add space between 3 children elements of which some are in separate components you can now do that:

```svelte
<div class="space-x-1">
<div>Status</div>
<Button>FAQ</Button>
<Button>Login</Button>
</div>
```

turns into:

```svelte
<div class="uno-7haszz">
<div>Status</div>
<Button>FAQ</Button>
<Button>Login</Button>
</div>

<style>
:global(.uno-7haszz > :not([hidden]) ~ :not([hidden])) {
--un-space-x-reverse: 0;
margin-left: calc(0.25rem * calc(1 - var(--un-space-x-reverse)));
margin-right: calc(0.25rem * var(--un-space-x-reverse));
}
</style>
```

### Nested Component styles

You can add the `class` prop to a component which which places them on to an element using `class="{$$props.class} foo bar"`.

```svelte
<Button class="px-2 py-1">Login</Button>
```

turns into:

```svelte
<Button class="uno-4hshza">Login</Button>

<style>
:global(.uno-4hshza) {
padding-left:0.5rem;
padding-right:0.5rem;
padding-top:0.25rem;
padding-bottom:0.25rem;
}
</style>
```

### Conditional `class:` syntax

Class names added using Svelte's class directive feature, `class:text-sm={bar}`, will also be compiled. No need to add `extractorSvelte` and custom extractors will not be used by this mode.

```svelte
<div class:text-sm={bar}>World</div>
```

turns into:

```svelte
<div class:uno-2hashz={bar}>World</div>

<style>
:global(.uno-2hashz) {
font-size: 0.875rem;
line-height: 1.25rem;
}
</style>
```

The class directive shorthand usage of `class:text-sm` where `text-sm` is both a class and a variable is also supported. The plugin will change `class:text-sm` into `class:uno-2hshza={text-sm}`.

### Usage Summary

```svelte
<span class:logo />
<!-- This would work if logo is set as a shortcut in the plugin settings and it is a variable in this component. Note that it's class name will not be changed -->

<div class="bg-red-100 text-lg">Hello</div>

<div class:text-sm={bar}>World</div>
<div class:text-sm>World</div>

<div class="fixed flex top:0 ltr:left-0 rtl:right-0 space-x-1 foo">
<div class="px-2 py-1">Logo</div>
<Button class="py-1 px-2">Login</Button>
</div>

<style>
div {
--at-apply: text-blue-500 underline;
}
.foo {
color: red;
}
</style>
```

will be transformed into this:

```svelte
<span class:logo />

<div class="uno-1hashz">Hello</div>

<div class:uno-2hashz={bar}>World</div>
<div class:uno-2hashz={text-sm}>World</div>

<div class="uno-3hashz foo">
<div class="uno-4hashz">Logo</div>
<Button class="uno-4hashz">Login</Button>
</div>

<style>
:global(.uno-1hashz) {
--un-bg-opacity: 1;
background-color: rgba(254, 226, 226, var(--un-bg-opacity));
font-size: 1.125rem;
line-height: 1.75rem;
}

:global(.uno-2hashz) {
font-size: 0.875rem;
line-height: 1.25rem;
}

:global(.uno-3hashz) {
position: fixed;
display: flex;
}
:global([dir="ltr"] .uno-3hashz) {
left: 0rem;
}
:global([dir="rtl"] .uno-3hashz) {
right: 0rem;
}
:global(.uno-3hashz > :not([hidden]) ~ :not([hidden])) {
--un-space-x-reverse: 0;
margin-left: calc(0.25rem * calc(1 - var(--un-space-x-reverse)));
margin-right: calc(0.25rem * var(--un-space-x-reverse));
}

:global(.uno-4hashz) {
padding-left:0.5rem;
padding-right:0.5rem;
padding-top:0.25rem;
padding-bottom:0.25rem;
}

:global(.logo) {
/* logo styles will be put here... */
}

div {
--un-text-opacity: 1;
color: rgba(59, 130, 246, var(--un-text-opacity));
text-decoration-line: underline;
}

.foo {
color: red;
}
</style>
```

When this reaches the Svelte compiler it will remove the :global() wrappers, add it's own scoping hash just to the `div` and `.foo` rules.

## Example Project
To try this out in the example project here, install and then run dev.

- Tested with @sveltejs/kit@1.0.0-next.520 (release candidate)
- Includes usage example of `@unocss/transformer-directives`'s `--at-apply: text-lg underline` ability

## Notes

- In development, individual classes will be retained and hashed in place for ease of toggling on and off in your browser's developer tools. `class="mb-1 mr-1"` will turn into something like `class="_mb-1_9hwi32 _mr-1_84jfy4`. In production, these will be compiled into a single class name using your desired prefix, `uno-` by default, and a hash based on the filename + class names, e.g. `class="uno-84dke3`.
- Vite plugins can't yet be used to preprocess files emitted by `svelte-package` as it does not use Vite. Follow https://github.com/sveltejs/vite-plugin-svelte/issues/475 to see when this will be made possible. In the meantime a [temporary svelte preprocessor wrapper](https://www.npmjs.com/package/temp-s-p-u) was published to enable using `svelte-scoped` mode in component libraries and other context that don't use Vite.
- [UnoCSS Inspector doesn't work yet](https://github.com/unocss/unocss/issues/1718). PR's welcome!
- Has not been tested with `Attributify` mode and other such innovations and probably won't work with them.

## Known Issues

- Having a commented out style tag (e.g. `<!-- <style>...</style> -->`) will prevent styles working for that component as they will be placed inside a useless tag.
- Placing `dark:` prefixed styles in a component with `<style global></style>` will not work. If anyone wants to fix, they can look at the compiled Svelte output and go from there.
12 changes: 12 additions & 0 deletions examples/sveltekit-scoped/src/app.css
@@ -0,0 +1,12 @@
body {
margin: 0;
}

:root {
color-scheme: light dark;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
6 changes: 6 additions & 0 deletions examples/sveltekit-scoped/src/app.html
Expand Up @@ -4,6 +4,12 @@
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />

<!-- Resets must be added here before any sveltekit injected styles -->
<link rel="stylesheet" href="%sveltekit.assets%/tw-reset.css">
<!-- Place other styles here that you want component based styles to be able to override -->
<link rel="stylesheet" href="%sveltekit.assets%/tw-prose.css" />

%sveltekit.head%
</head>
<body data-sveltekit-prefetch>
Expand Down
11 changes: 11 additions & 0 deletions examples/sveltekit-scoped/src/lib/Button.svelte
@@ -0,0 +1,11 @@
<script lang="ts">
export let onclick: () => any;
</script>

<button on:click={onclick} type="button"><slot /></button>

<style>
button {
--at-apply: font-semibold bg-red-100 hover:bg-red-200 dark:bg-red-700 dark:hover:bg-red-600 p-3 rounded;
}
</style>
14 changes: 14 additions & 0 deletions examples/sveltekit-scoped/src/lib/Counter.svelte
@@ -0,0 +1,14 @@
<script lang="ts">
let count = 0;
function increment() {
count += 1;
}
</script>

<button
class="w-250px focus:border-orange-800 bg-orange-{400 -
100} hover:bg-orange-400 color-orange-900 font-semibold rounded-xl p-4"
on:click={increment}
>
Clicks: {count}
</button>
6 changes: 6 additions & 0 deletions examples/sveltekit-scoped/src/lib/DarkModeToggle.svelte
@@ -0,0 +1,6 @@
<button
class="fixed top-1 right-1 p-2 text-lg opacity-75 hover:opacity-100"
on:click={() => window.document.body.classList.toggle("dark")}
>
<span class="dark:i-ri-moon-line i-ri-sun-line" />
</button>
5 changes: 5 additions & 0 deletions examples/sveltekit-scoped/src/lib/Toggle.svelte
@@ -0,0 +1,5 @@
<script>
let show = false;
</script>

<slot {show} toggle={() => (show = !show)} />
6 changes: 6 additions & 0 deletions examples/sveltekit-scoped/src/lib/index.ts
@@ -0,0 +1,6 @@
// Placeholder until https://github.com/sveltejs/vite-plugin-svelte/issues/475 is solved and the Vite plugin can be utilized by `svelte-package` in creating component libraries

// export { default as Button } from './Button.svelte'
// export { default as Counter } from './Counter.svelte'
// export { default as DarkModeToggle } from './DarkModeToggle.svelte'
// export { default as Toggle } from './Toggle.svelte'
18 changes: 16 additions & 2 deletions examples/sveltekit-scoped/src/routes/+layout.svelte
@@ -1,3 +1,17 @@
<slot />
<script>
import "../app.css";
import Preflights from "./Preflights.svelte";
import DarkModeToggle from "$lib/DarkModeToggle.svelte";
</script>

<Preflights />

<div
class="bg-white text-gray-900 dark:bg-gray-900 dark:text-white px-3 py-15 min-h-100vh transition"
>
<main class="max-w-800px mx-auto text-center">
<slot />
</main>
<DarkModeToggle />
</div>

<style uno:preflights uno:safelist global></style>