A svelte preprocess minimize CSS class footprint with statical analysis for unbeliveable performance gain. :rocket: :rocket: :rocket:
Although this preprocess has been tested extensively, this is not stable yet and expect a bug or two given the complexity of the whole process. Do open an issue and let me know if something go south, and I will try to fix as fast as I can.
Given the following input:
<style>
.hello{
color: red;
font-size: 12px;
}
@media screen and (min-width: 600px) {
.hello{
margin: none;
}
}
.world{
color: red;
font-size: 16px;
}
</style>
<h1 class="hello">This preprocess</h1>
<h2 class="world">
<p>does magic!</p>
</h2>
This preprocess will turn this into the following output in Svelte:
<style>
:global(.a){
color: red;
}
:global(.b){
font-size: 12px;
}
:global(.c){
font-size: 16px;
}
@media screen and (min-width: 600px) {
:global(.d){
margin: none;
}
}
</style>
<h1 class="a b d">This preprocess</h1>
<h2 class="a c">
<p>does magic!</p>
</h2>
And Svelte will generate the following HTML and CSS(should be external stylesheet, using <style>
here for demo purpose):
<style>
.a {
color: red;
}
.b {
font-size: 12px;
}
.c {
font-size: 16px;
}
@media screen and (min-width: 600px) {
.d {
margin: none;
}
}
</style>
<h1 class="a b d">This preprocess</h1>
<h2 class="a c">
<p>does magic!</p>
</h2>
This preprocess will transform rules with the following selectors:
Type Selector h1
Id Selector #foo
Class Selector .foo
Attribute Selector li[title]
Descendant combinator .foo .bar
Child combinator .foo>.bar
Adjacent sibling combinator .foo+.bar
General sibling combinator .foo~.bar
Pseudo class .foo:hover
Pseudo selector .foo::before
:not()
selector a:not(.hello)
This preprocess currently doesn't handle the following selectors. It will not transform rules with theses selectors.
multiple pseudo selectors (e.g. .foo:active .bar:hover
)
:global()
selector (as svelte/compiler
doesn't parse its value correctly right now)
Unlike other CSS hashing solutions that hash based on the content of the stylesheet(e.g. CSS Module), Glory hashes based on declarations(declaration refers to the combination of property and value, like font-size:20px
).
With that, classnames are now irrelavent and that abstration layer is removed. You are basically writing declarations to the component directly, as if using inline style
attribute, but everything in a nicer way.
As the hash is built based on declarations, you can maximize the compression gain only if you share the hash across all components.
Furthermore, with :global()
, svelte will remove all injected .svelte-xxxxxx
hash, compressing the CSS footprint to the very fine edge.
Despite turning everything global, during compile time all pre-transformed classnames are additionally hashed by filename, therefore no additional hash is needed in the classname.
This test verifies the scoping implementation.
Yes they are lazy-loaded by default. Declarations that are found in both components and __layout.svelte
will be hoisted to it, or else it will be kept in its own stylesheet. Therefore the lazy-loaded feature of Svelte is preserved.
However, you may observe a greater reduction in CSS size by serving all of them in __layout.svelte
with opts.lazyLoad
This preprocessor will only affect all CSS defined inline inside svelte-component.
All unrelated rules will be kept untouched.
npm install glory-svelte-preprocess
gloryPreprocess
takes an object of options.
opts.lazyLoad
(default to true
)
Setting this to false
will generate all classes in __layout.svelte
Just import this preprocessor in your svelte.config.js
import gloryPreprocess from "glory-svelte-preprocess";
import preprocess from "svelte-preprocess";
const config = {
preprocess: [gloryPreprocess(), preprocess()],
kit: {
target: "#svelte",
adapter: adapter(),
},
};
export default config;
If you are using any preprocessor(e.g. ScSS
, PostCSS
) that works with non-standard CSS syntax, set up the preprocess with a wrapper.
The world fastest framework agonistic CSS-in-JS library.