Compiler

The Compiler is the core for generating CSS with Stylify. This tool generates CSS and rewrites (mangles) selectors. Also when you need to add Variables, Components, custom Macros and etc, you do that in a Compiler config.

Syntax

Syntax is similar to CSS property:value with a few differences:

  • Use _ (one underscore) for a space and ^ (a hat) for a quote
  • The default syntax pattern is <screen>:<pseudo classes>:<property>:<value>. Sceens and pseudo classes are optional.
  • Screns can be combined using logical operands:
    • Logical AND: &&
    • Logical OR: ||
color:blue => blue color
hover:color:blue => blue color after hover
lg:color:blue => blue color for from selected screen
lg:hover:color:blue => blue color after hover from selected screen

lg&&dark:color:red => large screen and prefer-color-scheme dark
minw740px||landscape:color:blue => for min width or landscape

Compiler Configuration

dev

If dev is set to true, the generated CSS will contain new lines and spaces to be more readable, selectors in generated CSS will not be mangled and if any variable is missing, only a warning will be shown in the console.

const compilerConfig = {
	dev: true
};

macros

Macros are used to match selectors and generate CSS according to the match. The key inside the object can be a string or a regular expression.

Eeach matched selector is automatically mangled if enabled: color:rgb(255,255,255) => ab.

const compilerConfig = {
	macros: {
		'color:(\\S+?)': (match) => {
			// color:blue => will create => color: blue
			return {'color': match.getCapture(0)}
		},
	},
};

Usage:

<span class="color:red"></span>
<div class="color:#000"></span>
<div class="color:rgb(255,255,255)"></span>

selectorsPrefix

This option allows you to set for example u- (as utility) prefix. This prefix will be joined with macros during matching.

When you define such prefix, you must use it anywhere (custom selectors, components, utilities). Otherwise, selectors will not be matched and generated.

Thanks to this feature you can use Stylify within your existing application without collision with already existing selectors.

const compilerConfig = {
	selectorsPrefix: 'u-'
};

And in the code:

<div class="
	u-color:blue
	hover:u-color:red
	[a]{u-color:blue}
"></div>

mangleSelectors

If the mangle selectors option is set to true, the selectors in CSS will be mangled from long to short.

This only configures the Compilation Result so it generates minified CSS selectors. To rewrite selectors in files, you need to call compiler.rewriteSelectors(content) method.

The rewriteSelectors method is executed automatically within the Unplugin, Bundler, Astro, Nuxt and Nuxt Module packages. You have to call it only, if you want to work with the Compiler directly.

const compilerConfig = {
	mangleSelectors: true
};
const compiler = new Compiler(config);

compiler.rewriteSelectors(content);

mangledSelectorsPrefix

This prefix is added before all mangled selectors. You can use any non-numeric character, for example _ (an underscore).

This feature prevents collision of mangled selectors with those you may already have in your application. Use it only, when there is such collision.

const compilerConfig = {
	mangledSelectorsPrefix: '_'
};
<div class="color:blue"></div>

<!-- With configuration -->
<div class="_a"></div>

<!-- Without configuration -->
<div class="a"></div>

variables

Variables can be used in a selector or accessed inside a macro.

const compilerConfig = {
	variables: {
		blue: '#01befe',
		shadow: '0 8px 32px -8px rgb(0, 0, 0, 0.2)',
		// When variable is an object, Stylify CSS tries to find screen for it
		// You can use any screen you have defined in the screens
		dark: { blue: 'lightblue' },
		md: { fontSize: 24px },
		'minw640px': { fontSize: 32px },
		// When screen is not found, it falls back to a random custom selector
		'.dark': { blue: 'lightblue' },
		':root[data-theme="dark"]': { blue: 'lightblue' }
	},
	// If you want to disable CSS variables, set this option to false
	cssVariablesEnabled: true,
	// By default, variables are automatically injected into the generated CSS as CSS variables.
	// You can change this behavior by setting the option below to false
	injectVariablesIntoCss: true
};

Usage:

<span class="color:$blue"></span>

externalVariables

In case you have some CSS variables defined elsewhere than in the Stylify config, you can mark them add them as external.

External variables cannot be used within helpers because their value cannot be accessed and processed.
const compilerConfig = {
	externalVariables: [
		// Simple string check
		'some-color',
		// Define callback to specify more flexible check.
		// This will for example mark every variable that starts with md-
		// as external.
		(variable) => variable.startsWith('md-') ? true : undefined
	],
	// If you have a lot of external variables and you don't want to bother by mapping them,
	// you can change the warning level
	// 'silent' => disables warning completely
	// 'warn' => is default for development
	// 'error' => is default for production
	undefinedVariableWarningLevel: 'silent'
};

Usage:

<span class="
	color:$some-color
	background:$md-sys-color-primary
	border-color:$md-sys-color-tertiary
"></span>

keyframes

Keyframes in Stylify CSS are defined with the same syntax like in the CSS.

Keyfames can also be defined within a comment within a file using content options.

const compilerConfig = {
	keyframes: {
		fadeIn: 'from { opacity: 0; } to { opacity: 1; }',
		fadeOut: 'from { opacity: 1; } to { opacity: 0; }',
		shadowPulse: `
			from { box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2); }
			to { box-shadow: 0 0 0 20px rgba(0, 0, 0, 0); }
		`
	}
};
<span class="animation:fadeIn_2s_infinite">Fade In</span>
<span class="animation:fadeOut_2s_infinite">Fade Out</span>
<span class="animation:shadowPulse_2s_infinite">Shadow pulse</span>

screens

Screens are used to generate media queries. The key can be a string or a regular expression. You can use predefined screens or define your own.

Screns can be combined using logical operands:

  • Logical AND: &&
  • Logical OR: ||
const compilerConfig = {
	screens: {
		'sm': '(min-width: 400px)',
		// Screens can also be functions
		// That allows you to make as flexible screen as possible
		'minw\\w+': (screen) => `(min-width: ${screen.fullMatch.replace('minw', '')})`
	}
};

Usage:

<span class="sm:color:darkred"></span>
<div class="minw640px:color:$blue"></span>
<div class="minw80rem:color:darkgreen"></span>

components

Components can decrease the amount of selectors in a template. They can be defined in the file where they are used or in the config. When defined using content-option, it expects javascript object without surrounding brackets. When defining a component, you can also use nested syntax

Components can be also defined directly in files using content options.

When you define a component or macro like link this selector can have a collision in production with selector like sidebar-link, when mangling selectors. This selector will be replaced as a (for link) and sidebar-a (for section). You can prevent this behavior by configuring sidebar-section in ignoredAreas option.
const compilerConfig = {
	components: {
		// selector => dependencies
		'button': 'padding:4px background:black color:white hover:background:grey',
		'container': `
			max-width:1024px
			margin:0_auto
			md:max-width:1280px
		`,
		// You can define multiple components in one key, just separate them by "," (comma)
		'wrapper, footer': 'padding:24px',
		// When one component is defined multiple times, the selectors are merged
		// When selectorsChain is defined the last one is applied
		'wrapper': 'margin-top:24px',
		'button--big': `
			&.btn {
				font-size:48px
			}
		`,
		// Dynamic components
		// When the function has a callback, it accepts matches from regular expression
		// variables, helpers and if is dev environment.
		// This way you can define component, that returns selectors based on matches
		// from regular expression.
		'title(?:--(\\S+))?': ({ matches, variables, helpers, dev }) => {
			const color = matches[1] ?? '#000';
			return `font-size:24px${color ? ` color:${color}` : ''}`;
		},
	}
};

Usage:

<span class="button"></span>
<div class="container"></div>
<!-- Dynamic Components -->
<div class="title"></div>
<div class="title--#06f">
<div class="title--$red">

customSelectors

Custom selectors allows you to write CSS selectors for elements. When configuring pseudo class for direct element, you can use the pseudo class directly. When the selector is not direct, then the pseudo class should be on the selector and not in the Stylify CSS selector. Check out the examples.

Custom selectors can be also defined directly in files using content options.

const compilerConfig = {
	customSelectors: {
		// selector => dependencies
		'article': 'font-size:16px line-height:28px color:#222',
		'article h1, article h2': 'color:blue',
		// For indirect selectors with pseudo class like `div > button`, `article a`
		'article a:hover': 'color:blue'
		'article a:hover i': 'color:white'
		// For direct selectors with pseudo class like a, input or a.button and a.link
		'a': 'color:green hover:color:blue',
		'a.link': 'color:green hover:color:red'
	}
};

Usage:

<article></article>

Nested syntax for custom selectors

You can nest selectors using SCSS-like syntax. To create the selector is the same like in CSS. To refer the upper level use the & character. To keep things simple, the only feature is nesting and chaining. The syntax is the same for content options. The pseudo classes like :hover works the same like in the example above. The example below will generate the following:

  • header { width:800px }
  • header nav { font-size:14px }
  • header.fixed {}
  • .docs header { background:blue }
  • header h1, header h2 { font-family:arial }
const compilerConfig = {
	customSelectors: {
		'header': `
			width:800px
			nav {
				font-size:14px
			}
			&.fixed {
				position:fixed
			}
			.docs & { background:blue }
			h1, h2 { font-family:arial }
		`
	}
}

Custom selectors can be also written directly into the class attributes. The syntax is the following [selector]{macros}. Instead of a space use the _ underscore. For a quote, use ^. And to split different macros use ;. The example below will generate the following:

  • .docs [.docs_&]{font-size:14px;color:#222} {font-size:14px; color:#222}
  • [h1,h2]{margin-top:0} h1, [h1,h2]{margin-top:0} h2 { margin-top:0 }

For pseudo classes:

  • [a::after]{content:^Hello_World^} a::after {content:'Hello World'}
  • [a]{hover:color:steelblue} a:hover {color:steelblue}
  • [a:hover]{color:steelblue} a:hover {color:steelblue}
  • [&:hover_a]{color:steelblue}:hover a {color:steelblue}
<article class="
	[.docs_&]{font-size:14px;color:#222}
	[h1,h2]{margin-top:0}

	[a]{hover:color:steelblue}
	[a:hover]{color:steelblue}
	[&:hover_a]{color:steelblue}
"></article>

helpers

Helpers are functions that can be called when a selector is matched and its properties are being generated.

const compilerConfig = {
	helpers: {
		shortcut(value) {
			const shortcuts = {
				'bgc': 'background-color',
				'zi': 'z-index'
			};

			return value in shortcuts ? shortcuts[value] : value;
		},
		joinText(...texts) => '"' + texts.join(' ') + '"'
	},
	macros: {
		'(bgc|zi):(\\S+?)'(match) {
			const property = this.helpers.shortcut(match.getCapture(0));
			return {[property]: match.getCapture(1)}
		}
	}
}

Usage:

<div class="
	zi:2 bgc:red
	color:lighten(#000,10)
	content:joinText(^Custom^,^Long_Text^)
"></div>

selectorsAreas

In case you want to rewrite selectors in any framework specific class attribute, you must define that attribute to be matched. By default Stylify CSS supports a few syntaxes from HTML, Vue, React, Lit, AlpineJS, Svelte, Astro, Symfony and Nette. In case, some of the class attributes wasn’t matched, configure selectorsAreas option with a regular expressions to match it:

const compilerConfig = {
	selectorsAreas: [
		// Vue.js
		/(?:^|\s+)(?:v-bind)?:class="([^"]+)"/,
		// React
		/(?:^|\s+)className="([^"]+)"/
	]
};

ignoredAreas

In case you need to mark a code to be ignored during compilatio, you can use ignored areas.

stylify-ignore and stylify-runtime-ignore are by default areas you can use to remove content from compilation.

Also the following elements are ignored (only without attributes): code, head, pre, script, style.

Note that matching tags or areas using regular expressions is not reliable in some situations, therefore try to use the stylify-ignore as it is the most reliable option.

Usage

<!-- stylify-ignore -->
Everything inside will be ignored
<div class="color:red"></div>
<!-- /stylify-ignore -->

pregenerate

The pregenerate option allows you to add some content into the compilation process.

const compilerConfig = {
	pregenerate: 'color:red color:blue width:100%'
};

contentOptionsProcessors

Some configuration options can be defined directly in a file using content options. It’s good to keep the definition for example of a component with its HTML. Every content option, even when defined in a file, is registered globally. It’s also not scoped in any way. So if you define a component, like button, it can be used anywhere in the app.

Content options can be defined anywhere within a file like this:

stylify-<option name>
  Content option content
/stylify-<option name>

The best way is to define the content options within a multiline comments, so it is not visible on the page.

Use the comment style that is valid for the file type where you define the component.
<!--

// Components expects a valid javascript object as value without surrounding brackets
stylify-components
	button: `font-size:24px padding:4px`,
	'button--big': `
		&.btn {
			font-size:48px
		}
	`
/stylify-components

// Variables expects a valid javascript object as value without surrounding brackets
stylify-variables
	blue: `#01befe`
/stylify-variables

// Keyframes expects a valid javascript object as value without surrounding brackets
stylify-keyframes
	fadeIn: 'from { opacity: 0; } to { opacity: 1; }',
	fadeOut: 'from { opacity: 1; } to { opacity: 0; }',
	shadowPulse: `
		from { box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.2); }
		to { box-shadow: 0 0 0 20px rgba(0, 0, 0, 0); }
	`
/stylify-keyframes


// Custom selectors expects a valid javascript object as value without surrounding brackets
stylify-customSelectors
	article: `font-size:24px`
/stylify-customSelectors

// Pregenerate expects a string
stylify-pregenerate
	border-top:1px_solid_#444
/stylify-pregenerate

// Screens expects a valid javascript object as value without surrounding brackets
stylify-screens
	'testScreen': '(min-width: 123px)',
	'dynamic\\w+': (screen) => `(max-width: ${screen.fullMatch.replace('dynamic', '')})`
/stylify-screens
-->

Adding custom content option processor

import { hooks } from '@stylify/stylify'

// You can use Stylify compiler to get any option
hooks.addListener('compiler:processContentOption:myOption', ({
	contentOptions,
	option,
	value
}) => {

});

Compilation Result

Compilation result can be created or configured and passed into the Compiler as a second argument. By this approach, you can change the compilation behavior and extend the functionality.

Be aware that if you modify compilation result or create a new one with a wrong configuration, you can break the whole compilation process.
const compilationResult = new CompilationResult({
	// All options are optional
	dev: false,
	// If reconfigurable is set to false, the configuration will not change
	reconfigurable: false,
	// This function is responsible for sorting screens before the CSS is generated.
	// The argument is a Map type and the function must also return a Map type.
	screensSortingFunction: (screensList) => { return screensList },
	// If mangle selectors is true, selectors within the CSS will be manhled
	mangleSelectors: false,
});

CSS Record

Css record can be accessed only through a hook compilationResult:configureCssRecord. The CSS record is responsible for keeping the CSS tree and how the selectors are joined, managed and etc.

hooks.addListener('compilationResult:configureCssRecord', ({cssRecord}) => {
	// ...
});

Hooks

Stylify has a hookable system that allows you to modify extend the functionality.

  • compiler:beforeMacrosProcessed: Before the content is processed and macros matched
  • compiler:afterMacrosProcessed: Right after the beforeMacrosProcessed
  • compiler:compilationResultConfigured: Triggered when compilation result is ready
  • compiler:newMacroMatch: This hook is triggered when a macro is matched within the content
  • compiler:processContentOption:[option]: Triggerd when processing content option. The [option] must be replaced by the name of content option like customOption if ou want to process your own options
  • compilationResult:configureCssRecord: This hook is called when CSS record is created. You can for example set the scope
  • cssRecord:addProperty: This is called right before the CSS property:value is added.
  • cssRecord:cssGenerated: Triggered when the CSS was generated
import { hooks } from '@stylify/stylify';

hooks.addListener('hoook:name', (options) => {});

Custom compilation process

import { Compiler } from '@stylify/stylify';

const content = '<div class="color:blue"></div>';

const compiler = new Compiler();
const compilationResult = compiler.compile(content);
const css = compilationResult.generateCss();
// If the third parameter is set and if it is true (default) it rewrites selectors
// only in areas defined in selectorsAreas and not in whole content
const mangledContent = compiler.rewriteSelectors(content, compilationResult, true);