Style loader

The style loader works with the styled utility (a clone of the api of styled-component). Every module targeted by this loader should export classnames, generated with the styled utility :

import { className } from 'https://deno.land/x/frugal/packages/loader_style/styled.ts';

export const item = className('item').styled`
    color: red;
`;

export const selected = className('selected').styled`
    color: blue;
`;

export const list = className('list').styled`
    padding: 0;
`;

If this module is caught by the style loader (meaning its name matches the loader pattern), the following css will be generated :

.item-l6cy2y {
    color: red;
}

.selected-ug9bde {
    color: blue;
}

.list-vngyoe {
    padding: 0
}

You can then import your style module and use it in your markup (using the cx utility, to generate a string from a smorgasbord of classnames) :

import { cx } from 'https://deno.land/x/frugal/packages/loader_style/styled.ts';
import { item, list, selected } from './MyComponent.style.ts';

export function MyComponent(items: any[]) {
    return `<ul className="${cx(list)}"}>
        ${items.map(item => {
            return `<li className="${cx(item, item.isSelected && selected)}>
        })}
    </ul>`;
}

the className utility will generate unique classnames. You can control the prefix of the classname, for easier debugging. Those class will be ordered by declaration order :

  • within the same module, classnames declared first are outputed first
  • amongst modules, classnames from modules imported first are outputed first.

The style loader will provide to the loaderContext a string containing the url of the generated css file. You can therefore get the url of the css file in the getContent function of your page descriptor :

export function getContent(
    { loaderContext }: frugal.GetContentParams<Path, Data>,
) {
    const cssFileUrl = loaderContext.get('style');

    // ...
}

Transformer

The style loader has no notion of css syntax, it simply aggregates what is given to him. This means that you can "customize" the flavor of css you want, via the transform function. Here for example, we use the stylis preprocessor :

import { Config, page } from 'https://deno.land/x/frugal/packages/core/mod.ts';
import * as stylis from 'https://esm.sh/stylis@4.0.13';
import { StyleLoader } from 'https://deno.land/x/frugal/packages/loader_style/mod.ts';

import * as myPage from './pages/myPage.ts';

const self = new URL(import.meta.url);

export const config: Config = {
    self,
    outputDir: './dist',
    pages: [
        page(myPage),
    ],
    loader: [
        new StyleLoader({
            test: (url) => /\.style\.ts$/.test(url.toString()),
            transform: (content) => {
                return stylis.serialize(
                    stylis.compile(content),
                    stylis.middleware([stylis.prefixer, stylis.stringify]),
                );
            },
        }),
    ],
};

With this setup, the following module, using non-standard syntax :

import { className } from 'https://deno.land/x/frugal/packages/loader_style/styled.ts';

export const item = className('item').styled`
    color: red;
`;

export const list = className('list').styled`
    padding: 0;

    ${item} {
        color: blue;
    }
`;

should output the following style

.item-l6cy2y {
    color: red;
}

.list-vngyoe {
    padding: 0
}

.list-vngyoe .item-l6cy2y {
    padding: 0
}

Interaction with script loader

If you import a style module in a script (you have to toggle a class using js for exemple), the script loader will bundle the style module without complaining. This means that your bundle will now contain numerous non-compressible string describing some style that are useless to your bundle, since those styles are already in the .css file generated by the style loader.

When bundling a style module, we only want to bundle the generated classnames, not what was used to generate them. For this style module :

import { className } from 'https://deno.land/x/frugal/packages/loader_style/styled.ts';

export const item = className('item').styled`
    color: red;
    padding: 10em;
    width: 100px
    height: 30px;
    position: abosolute;
`;

const base = className('base').styled`
    font-size: 1rem;
    font-weight: 300;
    transform: translate(-50%, 50%);
`;

export const list = className('list').extends(base).styled`
    padding: 0;
`;

we want to bundle only this :

export const item = 'item-l6cy2y';
export const list = 'list-vngyoe base-ucdg1u';

In order to do so, the script loader accepts some transformers. Each transformer will run on modules matching a test function, and transform the code of the module before bundling. You can use the styleTransfromer exposed by the style loader module to transform any style modules :

import { ScriptLoader } from 'https://deno.land/x/frugal/packages/loader_script/mod.ts';
import { StyleLoader, styleTransformer } from 'https://deno.land/x/frugal/packages/loader_style/mod.ts';

function isStyleModule(url: string|URL) {
    return /\.style\.ts$/.test(url.toString())
}

const config = {
    //...
    loaders: [
        new StyleLoader({
            test: isStyleModule,
        })
        ScriptLoader({
            test: (url) => /\.script\.ts$/.test(url.toString()),
            formats: ['esm'],
            transformers: [{
                test: isStyleModule,
                transform: styleTransformer
            }],
        })
    ]
}