编译器

编译器是用Stylify生成CSS的核心。这个工具可以生成CSS并重写(mangles)选择器。另外,当你需要添加变量、组件、自定义宏等时,你可以在编译器的配置中完成。

语法

语法与CSS的property:value相似,但有一些区别:

  • 使用_(一个下划线)表示空格,^(一个帽子)表示引号。
  • 默认的语法模式是<屏幕>:<伪类>:<属性>:<值>。屏幕和伪类是可选的。
  • 屏幕可以使用逻辑操作数进行组合:
    • 逻辑和&&
    • 逻辑OR||
color:blue => 蓝色
hover:color:blue => 悬停后为蓝色
lg:color:blue => 选中屏幕时为蓝色
lg:hover:color:blue => 从选定的屏幕悬停后的蓝色

lg&&dark:color:red => 大屏幕和喜欢深色的颜色模式。
minw740px||landscape:color:blue =>最小宽度或横向

编译器配置

dev

如果dev被设置为 “true”,生成的CSS将包含新的行和空格,以提高可读性,生成的CSS中的选择器不会被篡改,如果缺少任何变量,在控制台中只显示一个警告。

const compilerConfig = {
	dev: true
};

macros

宏用于匹配选择器并根据匹配结果生成CSS。对象里面的键可以是一个字符串或一个正则表达式。

如果启用的话,每个匹配的选择器都会被自动处理: color:rgb(255,255,255) => ab.

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

使用方法:

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

selectorsPrefix

这个选项允许你设置例如 “u-”(作为效用)前缀。这个前缀将在匹配过程中与宏连接。

当你定义这种前缀时,你必须在任何地方使用它(自定义选择器、组件、实用程序)。否则,选择器将不会被匹配和生成。

由于这个功能,你可以在你现有的应用程序中使用Stylify,而不会与已有的选择器发生冲突。

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

而在代码中:

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

mangleSelectors

如果mangle selectors选项被设置为 “true”,那么CSS中的选择器将被从长变短。

这只是配置了编译结果,所以它生成的是最小化的CSS选择器。要重写文件中的选择器,你需要调用`compiler.rewriteSelectors(content)‘方法。

rewriteSelectors方法在Unplugin、Bundler、Astro、Nuxt和Nuxt Module包中自动执行。如果你想直接使用编译器,你必须调用它。

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

compiler.rewriteSelectors(content);

mangledSelectorsPrefix

这个前缀会被添加到所有混杂的选择器之前。你可以使用任何非数字字符,例如_(下划线)。

这个功能可以防止混杂的选择器与你的应用程序中已经有的选择器发生碰撞。只有在有这种碰撞的情况下才使用它。

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

<!-- 有了配置 -->
<div class="_a"></div>

<!-- 没有配置 --->
<div class="a"></div>

variables

变量可以在选择器中使用,也可以在宏里面访问。

const compilerConfig = {
	variables: {
		blue: '#01befe',
		shadow: '0 8px 32px -8px rgb(0, 0, 0, 0.2)',
		// 当变量是一个对象时,Stylify CSS会尝试为它寻找屏幕。
		// 你可以使用你在屏幕中定义的任何屏幕
		dark: { blue: 'lightblue' },
		md: { fontSize: 24px },
		'minw640px': { fontSize: 32px },
		// 当没有找到屏幕时,它会回到一个随机的自定义选择器上
		'.dark': { blue: 'lightblue' },
		':root[data-theme="dark"]': { blue: 'lightblue' }
	},
	// 如果你想禁用CSS变量,请将此选项设为false。
	cssVariablesEnabled: true,
	// 默认情况下,变量会作为CSS变量自动注入到生成的CSS中。
	// 你可以通过将下面的选项设置为false来改变这种行为
	injectVariablesIntoCss: true
};

使用方法:

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

externalVariables

如果你在其他地方定义了一些CSS变量,而不是在Stylify的配置中,你可以将它们标记为外部变量。

外部变量不能在帮助器中使用,因为它们的值不能被访问和处理。
const compilerConfig = {
	externalVariables: [
		// 简单的字符串检查
		'some-color',
		// 定义回调以指定更灵活的检查。
		// 例如,这将标记每一个以md-开头的变量。
		// 作为外部变量。
		(variable) => variable.startsWith('md-') ? true : undefined
	],
	// 如果你有很多外部变量,而且你不想麻烦地映射它们,
	// 你可以改变警告级别
	// 'silent' => 完全禁止警告
	// 'warn' => 是开发时的默认值。
	// 'error' => 默认用于生产。
	undefinedVariableWarningLevel: 'silent'
};

使用方法:

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

keyframes

Stylify CSS中的关键帧的定义与CSS中的语法相同。

关键帧也可以使用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">淡入</span>
<span class="animation:fadeOut_2s_infinite">淡出</span>
<span class="animation:shadowPulse_2s_infinite">阴影脉冲</span>

screens

屏幕是用来生成媒体查询的。关键可以是一个字符串或一个正则表达式。你可以使用预定义的屏幕或定义你自己的。

屏幕可以使用逻辑操作数进行组合:

  • 逻辑和&&
  • 逻辑OR||
const compilerConfig = {
	screens: {
		'sm': '(min-width: 400px)',
		// 屏幕也可以是函数
		// 这可以让你尽可能地制作灵活的屏幕
		'minw\\w+': (screen) => `(min-width: ${screen.fullMatch.replace('minw', '')})`
	}
};

使用方法:

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

components

组件可以减少模板中选择器的数量。它们可以被定义在使用它们的文件中,也可以在配置中。当使用content-option定义时,它期望javascript对象没有周围括号。 当定义一个组件时,你也可以使用嵌套语法

也可以使用内容选项直接在文件中定义组件。

当你定义一个组件或宏,如link,这个选择器在生产中可能会与sidebar-link这样的选择器发生碰撞,当混用选择器时。这个选择器将被替换为a(用于链接)和sidebar-a(用于部分)。你可以通过在ignoredAreas选项中配置sidebar-section来防止这种行为。
const compilerConfig = {
	components: {
		// 选择器 => 依赖性
		'button': 'padding:4px background:black color:white hover:background:grey',
		'container': `
			max-width:1024px
			margin:0_auto
			md:max-width:1280px
		`,
		// 你可以在一个键中定义多个组件,只要用","(逗号)隔开即可
		'wrapper, footer': 'padding:24px',
		// 当一个组件被多次定义时,选择器会被合并起来
		// 当选择器链被定义时,最后一个会被应用
		'wrapper': 'margin-top:24px',
		'button--big': `
			&.btn {
				font-size:48px
			}
		`,
		// 动态组件
		// 当函数有一个回调时,它接受来自正则表达式的匹配。
		// 变量,帮助器和如果是dev环境。
		// 这样,你可以定义一个组件,根据正则表达式的匹配结果返回选择器。
		// 来自正则表达式。
		'title(?:--(\\S+))?': ({ matches, variables, helpers, dev }) => {
			const color = matches[1] ?? '#000';
			return `font-size:24px${color ? ` color:${color}` : ''}`;
		},
	}
};

使用方法:

<span class="button"></span>
<div class="container"></div>
<!-- 动态组件 -->
<div class="title"></div>
<div class="title--#06f">
<div class="title--$red">

customSelectors

自定义选择器允许你为元素编写CSS选择器。 当为直接元素配置伪类时,你可以直接使用该伪类。当选择器不是直接的,那么伪类应该在选择器上,而不是在Stylify的CSS选择器中。请看例子。

自定义选择器也可以直接在文件中使用内容选项来定义。

const compilerConfig = {
	customSelectors: {
		// 选择器 => 依赖性
		'article': 'font-size:16px line-height:28px color:#222',
		'article h1, article h2': 'color:blue',
		// 对于带有伪类的间接选择器,如`div > button`, `article a`。
		'article a:hover': 'color:blue'
		'article a:hover i': 'color:white'
		// 对于带有伪类的直接选择器,如a、input或a.button和a.link
		'a': 'color:green hover:color:blue',
		'a.link': 'color:green hover:color:red'
	}
};

使用方法:

<article></article>

自定义选择器的嵌套语法

你可以使用类似SCSS的语法来嵌套选择器。 创建选择器的方法与CSS中的相同。使用&字符来引用上层。 为了保持简单,唯一的功能是嵌套和链式。语法与内容选项相同。像:hover这样的伪类与上面的例子中的工作原理相同。 下面的例子将生成以下内容:

  • 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 }
		`
	}
}

自定义选择器也可以直接写入类的属性中。语法如下[选择器]{宏观}。使用_下划线来代替空格。对于引号,使用^。而要分割不同的宏,则使用;。 下面的例子将生成以下内容:

  • .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 }

对于伪类:

  • [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

帮助器是当一个选择器被匹配并且其属性被生成时可以被调用的函数。

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 = helpers.shortcut(match.getCapture(0));
			return {[property]: match.getCapture(1)}
		}
	}
}

使用方法:

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

selectorsAreas

如果你想在任何框架的特定类属性中重写选择器,你必须定义该属性来进行匹配。 默认情况下,Stylify CSS支持 HTML, Vue, React, Lit, AlpineJS, Svelte, Astro, Symfony和Nette的一些语法。如果某些类属性没有被匹配,可以添加selectorsAreas选项,用正则表达式来匹配它。

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

ignoredAreas

如果你需要在编译过程中标记一个代码被忽略,你可以使用忽略的区域。

stylify-ignorestylify-runtime-ignore是默认的区域,你可以用它来移除编译的内容。

同时,以下元素也会被忽略(只是没有属性): code, head, pre, script, style

请注意,使用正则表达式匹配标签或区域在某些情况下是不可靠的,因此尽量使用stylify-ignore,因为它是最可靠的选项。

使用方法

<!-- stylify-ignore -->
里面的东西将被忽略
<div class="color:red"></div>
<!-- /stylify-ignore -->

pregenerate

pregenerate选项允许你在编译过程中添加一些内容。

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

contentOptionsProcessors

一些配置选项可以直接在文件中使用内容选项来定义。例如,保留一个组件的定义和它的HTML是很好的。 每一个内容选项,即使在文件中定义,也是全局注册的。它也没有任何范围。因此,如果你定义了一个组件,比如按钮,它可以在应用程序的任何地方使用。

内容选项可以在一个文件中的任何地方定义,就像这样:

stylify-<option name>
  内容选项内容
/stylify-<option name>

最好的方法是在多行注释中定义内容选项,这样它在页面上是不可见的。

使用对你定义组件的文件类型有效的注释样式。
<!--

// 组件希望有一个有效的javascript对象作为值,不需要周围的括号
stylify-components
	button: `font-size:24px padding:4px`,
	'button--big': `
		&.btn {
			font-size:48px
		}
	`
/stylify-components

// 变量需要一个有效的javascript对象作为值,不需要周围的括号。
stylify-variables
	blue: `#01befe`
/stylify-variables

// 关键帧希望有一个有效的javascript对象作为值,不需要周围的括号。
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


// 自定义选择器需要一个有效的javascript对象作为值,不需要周围的括号。
stylify-customSelectors
	article: `font-size:24px`
/stylify-customSelectors

// Pregenerate期望一个字符串
stylify-pregenerate
	border-top:1px_solid_#444
/stylify-pregenerate

// Screens希望得到一个有效的javascript对象作为值,不需要周围的括号。
stylify-screens
	'testScreen': '(min-width: 123px)',
	'dynamic\\w+': (screen) => `(max-width: ${screen.fullMatch.replace('dynamic', '')})`
/stylify-screens
-->

添加自定义内容选项处理器

import { hooks } from '@stylify/stylify'

// 你可以使用Stylify编译器来获取任何选项
hooks.addListener('compiler:processContentOption:myOption', ({
	contentOptions,
	option,
	value
}) => {

});

编译结果

编译结果可以被创建或配置,并作为第二个参数传入编译器。通过这种方法,你可以改变编译行为并扩展功能。

请注意,如果你修改了编译结果或用错误的配置创建了一个新的编译结果,你会破坏整个编译过程。
const compilationResult = new CompilationResult({
	// 所有选项都是可选的
	dev: false,
	// 如果可重新配置被设置为false,配置将不会改变
	reconfigurable: false,
	// 这个函数负责在生成CSS之前对屏幕进行排序。
	// 参数是一个Map类型,该函数也必须返回一个Map类型。
	screensSortingFunction: (screensList) => { return screensList },
	// 如果mangle selectors为true,CSS中的选择器将被处理。
	mangleSelectors: false,
});

CSS记录

Css记录只能通过钩子compilationResult:configureCssRecord访问。CSS记录负责保存CSS树以及选择器如何被连接、管理等。

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

钩子

Stylify有一个可钩住的系统,允许你修改扩展功能。

  • compiler:beforeMacrosProcessed: 在内容被处理和宏匹配之前
  • compiler:afterMacrosProcessed: 就在 “beforeMacrosProcessed “之后
  • compiler:compilationResultConfigured: 当编译结果准备好时被触发
  • compiler:newMacroMatch: 当内容中的宏被匹配时,该钩子被触发。
  • compiler:processContentOption:[option]: 在处理内容选项时触发。如果你想处理你自己的选项,“[option]“必须由内容选项的名称代替,如 “customOption”。
  • compilationResult:configureCssRecord: 这个钩子在创建CSS记录时被调用。例如,你可以设置范围
  • cssRecord:addProperty: 这个钩子在添加CSS属性:值之前被调用。
  • cssRecord:cssGenerated: 当CSS被生成时触发
import { hooks } from '@stylify/stylify';

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

自定义编译过程

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();
// 如果第三个参数被设置,如果它是真的(默认),它将重写选择器
// 只在selectorsAreas中定义的区域,而不是整个内容中。
const mangledContent = compiler.rewriteSelectors(content, compilationResult, true);