在开始新项目时,Sass 编译眨眼间就完成了。 这感觉很棒,尤其是在与 Browsersync 配合使用时,Browsersync 会在浏览器中为我们重新加载样式表。 但是,随着 Sass 数量的增加,编译时间也会随之增加。 这绝非理想状态。
当编译时间超过一两秒时,这确实令人头疼。 对我来说,这已经足够让我在漫长的一天结束时失去专注力。 因此,我想分享一个在 WordPress CSS 编辑器 Microthemer 中提供的解决方案,作为概念证明。
这是一篇两部分的文章。 第一部分针对 Sass 用户。 我们将介绍基本原理、性能结果和一个交互式演示。 第二部分介绍了 Microthemer 如何让 Sass 更快的具体细节。 并考虑如何将其作为 npm 包实施,以将快速、可扩展的 Sass 提供给更广泛的开发者社区。
Microthemer 如何瞬间编译 Sass
在某些方面,这种性能优化很简单。 Microthemer 只编译较少的 Sass 代码。 它不会干预 Sass 的内部编译过程。
为了向 Sass 编译器提供更少的代码,Microthemer 会了解整个代码库中使用的 Sass 实体,例如变量和 mixin。 当编辑选择器时,Microthemer 只会编译该单个选择器,以及任何相关的选择器。 如果选择器使用相同的变量,或者一个选择器扩展了另一个选择器,则它们是相关的。 使用此系统,Sass 编译速度与 3000 个选择器一样快,与少量选择器一样快。
性能结果
对于 3000 个选择器,编译时间约为 0.05 秒。 当然,它会有所不同。 有时它可能更接近 0.1 秒。 其他时候,编译速度快至 0.01 秒(10 毫秒)。
要亲自尝试,您可以 观看视频演示。 或者使用在线 Microthemer 操场(请参阅以下说明)。
在线 Microthemer 操场
在线操场让您自己轻松体验 Microthemer。
说明
- 访问 在线 Microthemer 操场.
- 通过 常规 → 首选项 → CSS / SCSS → 启用 SCSS 启用对 Sass 的支持。
- 访问 视图 → 全部代码编辑器 → 开启 以添加全局变量、mixin 和函数。
- 切换回主 UI 视图(视图 → 全部代码编辑器 → 关闭)。
- 通过目标按钮创建选择器。
- 通过字体属性组左侧的编辑器添加 Sass 代码。
- 在每次更改后,您可以在 视图 → 生成的 CSS → 上次 SCSS 编译 中查看 Microthemer 在编译过程中包含的代码。
- 要查看此功能如何大规模运行,您可以通过 包 → 导入 → CSS 样式表 将大型样式表中的普通 CSS 导入 Microthemer(目前不支持导入 Sass)。
您希望将其作为 npm 包吗?
Microthemer 的选择性编译技术也可以作为 npm 包提供。 但问题是,您认为有这种需求吗? 您的本地 Sass 环境是否需要提速? 如果是,请在下方留言。
本文的其余部分针对那些为社区开发工具的人。 以及那些可能对如何解决这一挑战感兴趣的人。
Microthemer 编译 Sass 的方法
我们很快就会进入一些代码示例。 但首先,让我们考虑主要应用目标。
1. 编译最少代码
如果编辑的选择器与其他选择器没有关系,则我们希望编译正在编辑的单个选择器,或者编译具有相关 Sass 实体的多个选择器——但绝不超过必要程度。
2. 对代码更改做出响应
我们希望消除等待 Sass 编译的任何感知。 我们也不希望在用户按键之间处理太多数据。
3. 相同的 CSS 输出
我们希望返回与完整编译生成的 CSS 相同的 CSS,但仅针对代码子集。
Sass 示例
以下代码将作为本文的参考点。 它涵盖了我们的选择性编译器需要处理的所有情况。 例如全局变量、mixin 副作用和扩展选择器。
变量、函数和 mixin
$primary-color: green;
$secondary-color: red;
$dark-color: black;
@function toRem($px, $rootSize: 16){
@return #{$px / $rootSize}rem;
}
@mixin rounded(){
border-radius: 999px;
$secondary-color: blue !global;
}
选择器
.entry-title {
color: $dark-color;
}
.btn {
display: inline-block;
padding: 1em;
color: white;
text-decoration: none;
}
.btn-success {
@extend .btn;
background-color: $primary-color;
@include rounded;
}
.btn-error {
@extend .btn;
background-color: $secondary-color;
}
// Larger screens
@media (min-width: 960px) {
.btn-success {
border:4px solid darken($primary-color, 10%);
&::before {
content: "\2713"; // unicode tick
margin-right: .5em;
}
}
}
Microthemer 界面
Microthemer 有两个主要的编辑视图。
视图 1:全部代码
我们以与普通 Sass 文件相同的方式编辑全部代码编辑器。 这就是全局变量、函数、mixin 和导入所在的地方。

视图 2:视觉
视觉视图采用单选择器架构。每个 CSS 选择器都是一个独立的 UI 选择器。这些 UI 选择器被组织成文件夹。

由于 Microthemer 对单个选择器进行分割,因此分析发生在非常细粒度的级别——一次一个选择器。

这里有一个快速的小测验问题。$secondary-color
变量在完整代码视图的顶部设置为 red
。那么为什么之前的屏幕截图中的错误按钮是蓝色的呢?提示:这与混合的副作用有关。稍后将详细介绍。
第三方库
衷心感谢以下 Microthemer 使用的 JavaScript 库的作者。
- Gonzales PE – 将 Sass 代码转换为抽象语法树 (AST) JavaScript 对象。
- Sass.js – 在浏览器中将 Sass 转换为 CSS 代码。它使用 Web Workers 在单独的线程上运行编译。
数据对象
现在进入具体细节。找出合适的 数据结构 需要一些反复试验。但一旦确定,应用程序逻辑就会自然地落位。因此,我们将首先解释主要数据存储,然后以对处理步骤的简要总结结束。
Microthemer 使用四个主要的 JavaScript 对象来存储应用程序数据。
projectCode
:存储所有项目代码,并将其划分为独立的项,用于单个选择器。projectEntities
:存储项目中使用的所有变量、函数、混合、扩展和导入,以及这些实体使用位置。connectedEntities
:存储代码段与项目 Sass 实体之间的连接关系。compileResources
:存储代码库更改后选择性编译数据。
projectCode
projectCode
对象允许我们快速检索 Sass 代码段。然后,我们将这些代码段组合成一个字符串以进行编译。
files
:在 Microthemer 中,它存储添加到前面提到的完整代码视图中的代码。在 npm 实现中,files
将与实际的 .sass 或 .scss 系统文件相关联。folders
:Microthemer 的 UI 文件夹,其中包含分段的 UI 选择器。index
:文件夹或文件夹内选择器的顺序。itemData
:项目的实际代码,将在下一段代码中进一步解释。
var projectCode = {
// Microthemer full code editor
files: {
full_code: {
index: 0,
itemData: itemData
}
},
// Microthemer UI folders and selectors
folders: {
content_header: {
index:100,
selectors: {
'.entry-title': {
index:0,
itemData: itemData
},
}
},
buttons: {
index:200,
selectors: {
'.btn': {
index:0,
itemData: itemData
},
'.btn-success': {
index:1,
itemData: itemData
},
'.btn-error': {
index:2,
itemData: itemData
}
}
}
}
};
.btn-success
选择器的 itemData
以下代码示例显示了 .btn-success
选择器的 itemData
。
sassCode
:用于构建编译字符串。compiledCSS
:存储已编译的 CSS,用于写入样式表或文档头部的样式节点。sassEntities
:单个选择器或文件的 Sass 实体。允许进行更改前后分析,并用于构建projectEntities
对象。mediaQueries
:与上述数据相同,但用于媒体查询中的选择器。
var itemData = {
sassCode: ".btn-success { @extend .btn; background-color: $primary-color; @include rounded; }",
compiledCSS: ".btn-success { background-color: green; border-radius: 999px; }",
sassEntities: {
extend: {
'.btn': {
values: ['.btn']
}
},
variable: {
primary_color: {
values: [1]
}
},
mixin: {
rounded: {
values: [1]
}
}
},
mediaQueries: {
'min-width(960px)': {
sassCode: ".btn-success { border:4px solid darken($primary-color, 10%); &::before { content: '\\2713'; margin-right: .5em; } }",
compiledCSS: ".btn-success::before { content: '\\2713'; margin-right: .5em; }",
sassEntities: {
variable: {
primary_color: {
values: [1]
}
},
function: {
darken: {
values: [1]
}
}
}
}
}
};
projectEntities
projectEntities
对象允许我们检查哪些选择器使用特定的 Sass 实体。
variable
、function
、mixin
、extend
:Sass 实体类型。- 例如
primary_color
:Sass 实体名称。Microthemer 会规范化带连字符的名称,因为 Sass 可以互换地使用连字符和下划线。 values
:声明值或实例的数组。实例由数字 1 表示。Gonzales PE Sass 解析器将数字声明值转换为字符串。因此,我选择使用整数 1 来标记实例。itemDeps
:使用 Sass 实体的选择器数组。这将在下一段代码中进一步解释。relatedEntities
:我们的rounded
混合具有更新全局$secondary-color
变量为blue
的副作用,因此错误按钮是蓝色的。这种副作用使得rounded
和$secondary-color
实体相互依赖。因此,当包含$secondary-color
变量时,也应该包含rounded
混合,反之亦然。
var projectEntities = {
variable: {
primary_color: {
values: ['green', 1],
itemDeps: itemDeps
},
secondary_color: {
values: ["red", "blue !global", 1],
itemDeps: itemDeps,
relatedEntities: {
mixin: {
rounded: {}
}
}
},
dark_color: {
values: ["black", 1],
itemDeps: itemDeps
}
},
function: {
darken: {
values: [1]
},
toRem: {
values: ["@function toRem($px, $rootSize: 16){↵ @return #{$px / $rootSize}rem;↵}", 1],
itemDeps: itemDeps
}
},
mixin: {
rounded: {
values: ["@mixin rounded(){↵ border-radius:999px;↵ $secondary-color: blue !global;↵}", 1],
itemDeps: itemDeps,
relatedEntities: {
variable: {
secondary_color: {
values: ["blue !global"],
}
}
}
}
},
extend: {
'.btn': {
values: ['.btn', '.btn'],
itemDeps: itemDeps
}
}
};
$primary-color
Sass 实体的 itemDeps
以下代码示例显示了 $primary-color
(primary_color
)变量的 itemDeps
。$primary-color
变量由 .btn-success
选择器的两种形式使用,包括 min-width(960px)
媒体查询中的选择器。
path
:用于从projectCode
对象中检索选择器数据。mediaQuery
:用于更新样式节点或写入 CSS 样式表。
var itemDeps = [
{
path: ["folders", 'header', 'selectors', '.btn-success'],
},
{
path: ["folders", 'header', 'selectors', '.btn-success', 'mediaQueries', 'min-width(960px)'],
mediaQuery: 'min-width(960px)'
}
];
connectedEntities
connectedEntities
对象允许我们找到相关的代码段。我们会在代码库更改后填充它。因此,如果我们要从 .btn
选择器中删除 font-size
声明,代码将从以下内容更改为:
.btn {
display: inline-block;
padding: 1em;
color: white;
text-decoration: none;
font-size: toRem(21);
}
…更改为以下内容
.btn {
display: inline-block;
padding: 1em;
color: white;
text-decoration: none;
}
然后,我们将 Microthemer 的分析存储在以下 connectedEntities
对象中。
-
changed
:更改分析,它捕获了对toRem
函数的删除。actions
:用户操作数组。form
:声明(例如$var: 18px
)或实例(例如font-size: $var
)。value
:声明的文本值,或实例的整数 1。
coDependent
:扩展选择器必须始终与扩展选择器一起编译,反之亦然。这种关系是相互依赖的。变量、函数和混合只是半依赖的。实例必须与声明一起编译,但声明不需要与实例一起编译。但是,为了简单起见,Microthemer 将它们视为相互依赖的。将来,将添加逻辑来过滤掉不必要的实例,但这是在第一个版本中省略的。related
:rounded
混合与$secondary-color
变量相关。它使用global
标志更新该变量。这两个实体是相互依赖的;它们应该始终一起编译。但在我们的示例中,.btn
选择器没有使用rounded
混合。因此,下面的related
属性没有填充任何内容。
var connectedEntities = {
changed: {
function: {
toRem: {
actions: [{
action: "removed",
form: "instance",
value: 1
}]
}
}
},
coDependent: {
extend: {
'.btn': {}
}
},
related: {}
};
compileResources
compileResources
对象允许我们按正确顺序编译代码子集。在上一节中,我们删除了 font-size 声明。以下代码显示了更改后 compileResources
对象的外观。
-
compileParts
:要编译的资源数组。path
:用于更新相关projectCode
项的compiledCSS
属性。sassCode
:用于构建sassString
以进行编译。我们在每个代码段中附加一个 CSS 注释(/*MTPART*/
)。此注释用于将组合的 CSS 输出拆分为cssParts
数组。
sassString
:编译为 CSS 的 Sass 代码字符串。cssParts
:CSS 输出形式为数组。cssParts
的数组键与compileParts
数组一致。
var compileResources = {
compileParts: [
{
path: ["files", "full_code"],
sassCode: "/*MTFILE*/$primary-color: green; $secondary-color: red; $dark-color: black; @function toRem($px, $rootSize: 16){ @return #{$px / $rootSize}rem; } @mixin rounded(){ border-radius:999px; $secondary-color: blue !global;}/*MTPART*/"
},
{
path: ["folders", "buttons", ".btn"],
sassCode: ".btn { display: inline-block; padding: 1em; color: white; text-decoration: none; }/*MTPART*/"
},
{
path: ["folders", "buttons", ".btn-success"],
sassCode: ".btn-success { @extend .btn; background-color: $primary-color; @include rounded; }/*MTPART*/"
},
{
path: ["folders", "buttons", ".btn-error"],
sassCode: ".btn-error { @extend .btn; background-color: $secondary-color; }/*MTPART*/"
}
],
sassString:
"/*MTFILE*/$primary-color: green; $secondary-color: red; $dark-color: black; @function toRem($px, $rootSize: 16){ @return #{$px / $rootSize}rem; } @mixin rounded(){ border-radius:999px; $secondary-color: blue !global;}/*MTPART*/"+
".btn { display: inline-block; padding: 1em; color: white; text-decoration: none;}/*MTPART*/"+
".btn-success {@extend .btn; background-color: $primary-color; @include rounded;}/*MTPART*/"+
".btn-error {@extend .btn; background-color: $secondary-color;}/*MTPART*/",
cssParts: [
"/*MTFILE*//*MTPART*/",
".btn, .btn-success, .btn-error { display: inline-block; padding: 1em; color: white; text-decoration: none;}/*MTPART*/",
".btn-success { background-color: green; border-radius: 999px;}/*MTPART*/",
".btn-error { background-color: blue;}/*MTPART*/"
]
};
为什么包含了四个资源?
full_code
:toRem
Sass 实体发生了更改,并且full_code
资源包含toRem
函数声明。.btn
:选择器被编辑了。.btn-success
:使用@extend .btn
,因此它必须始终与.btn
一起编译。组合选择器变为.btn, .btn-success
。.btn-error
:这也使用@extend .btn
,因此出于与.btn-success
相同的原因,它必须包含在内。
两个选择器未包含在内,因为它们与.btn
选择器无关。
.entry-title
.btn-success
(在媒体查询内)
递归资源收集
除了数据结构之外,最耗时的挑战是弄清楚如何提取正确的 Sass 代码子集。当一段代码连接到另一段代码时,我们需要检查第二段代码的连接。这是一个连锁反应。为了支持这一点,以下gatherCompileResources
函数是递归的。
- 我们循环遍历
connectedEntities
对象,直到 Sass 实体名称级别。 - 如果函数或 mixin 具有副作用(例如更新全局变量),我们使用递归。
checkObject
函数返回特定深度的对象的 value,如果不存在 value 则返回 false。updateObject
函数设置特定深度的对象的 value。- 我们使用
absoluteIndex
作为键,将依赖资源添加到compileParts
数组中。 - Microthemer 通过将文件夹索引添加到选择器索引来计算
absoluteIndex
。这是有效的,因为文件夹索引以数百递增,每个文件夹的选择器数量最大为 40,少于一百。 - 如果添加到
compileParts
数组中的资源也具有共同依赖关系,我们使用递归。
function gatherCompileResources(compileResources, connectedEntities, projectEntities, projectCode, config){
let compileParts = compileResources.compileParts;
// reasons: changed / coDependent / related
const reasons = Object.keys(connectedEntities);
for (const reason of reasons) {
// types: variable / function / mixin / extend
const types = Object.keys(connectedEntities[reason]);
for (const type of types) {
// names: e.g. toRem / .btn / primary_color
const names = Object.keys(connectedEntities[reason][type]);
for (const name of names) {
// check side-effects for Sass entity (if not checked already)
if (!checkObject(config.relatedChecked, [type, name])){
updateObject(config.relatedChecked, [type, name], 1);
const relatedEntities = checkObject(projectEntities, [type, name, 'relatedEntities']);
if (relatedEntities){
compileParts = gatherCompileResources(
compileResources, { related: relatedEntities }, projectEntities, projectCode, config
);
}
}
// check if there are dependent pieces of code
const itemDeps = checkObject(projectEntities, [type, name, 'itemDeps']);
if (itemDeps && itemDeps.length > 0){
for (const dep of itemDeps) {
let path = dep.path,
resourceID = path.join('.');
if (!config.resourceAdded[resourceID]){
// if we have a valid resource
let resource = checkObject(projectCode, path);
if (resource){
config.resourceAdded[resourceID] = 1;
// get folder index + resource index
let absoluteIndex = getAbsoluteIndex(path);
// add compile part
compileParts[absoluteIndex] = {
sassCode: resource.sassCode,
mediaQuery: resource.mediaQuery,
path: path
};
// if resource is co-dependent, pull in others
let coDependent = getCoDependent(resource);
if (coDependent){
compileParts = gatherCompileResources(
compileResources, { coDependent: coDependent }, projectEntities, projectCode, config
);
}
}
}
}
}
}
}
}
return compileParts;
}
应用程序流程
我们现在已经涵盖了技术方面。要了解这一切如何联系在一起,让我们来了解一下数据处理步骤。
从击键到样式渲染
- 用户击键会触发文本区域更改事件。
- 我们将正在编辑的单个选择器转换为
sassEntities
对象。这允许与预编辑 Sass 实体进行比较:projectCode > dataItem > sassEntities
。 - 如果任何 Sass 实体发生了变化
- 我们更新
projectCode > dataItem > sassEntities
。 - 如果
@extend
规则发生了变化- 我们搜索
projectCode
对象以查找匹配的选择器。 - 我们将匹配选择器的
path
存储在当前数据项上:projectCode > dataItem > sassEntities > extend > target > [ path ]
。
- 我们搜索
- 我们通过循环遍历
projectCode
对象来重建projectEntities
对象。 - 我们用更改分析填充
connectedEntities > changed
。 - 如果存在
extend
、variable
、function
或mixin
实体- 我们用相关实体填充
connectedEntities > coDependent
。
- 我们用相关实体填充
- 我们更新
- 递归的
gatherCompileResources
函数使用connectedEntities
对象来填充compileResources
对象。 - 我们将
compileResources > compileParts
数组连接成一个单独的 Sass 字符串。 - 我们将单个 Sass 字符串编译成 CSS。
- 我们使用注释分隔符将输出拆分成一个数组:
compileResources > cssParts
。此数组通过匹配的数组键与compileResources > compileParts
数组对齐。 - 我们使用资源路径来更新
projectCode
对象以包含已编译的 CSS。 - 我们将给定文件夹或文件的 CSS 写入文档头中的样式节点,以立即渲染样式。在服务器端,我们将所有 CSS 写入单个样式表。
npm 注意事项
对于 npm 包,有一些额外的注意事项。使用典型的 NodeJS 开发环境
- 用户会将选择器作为较大文件的一部分进行编辑,而不是单独编辑。
- Sass 导入很可能会发挥更大的作用。
代码分割
Microthemer 的可视视图分割了单个选择器。这使得将代码解析为sassEntities
对象非常快。解析整个文件可能会有不同的情况,尤其是大型文件。
也许存在虚拟分割系统文件的方法?但假设无法避免解析整个文件。或者这对于第一个版本来说是合理的。也许建议最终用户保持 Sass 文件较小以获得最佳效果就足够了。
Sass 导入
在撰写本文时,Microthemer 不会分析导入文件。相反,它会在选择器使用任何 Sass 实体时包含所有 Sass 导入。这是一个临时的第一个版本解决方案,在 Microthemer 的背景下是可以的。但我认为 npm 实现应该跟踪所有项目文件中的 Sass 使用情况。
我们的projectCode
对象已经有一个用于存储文件数据的files
属性。我建议根据主 Sass 文件计算文件索引。例如,第一个@import
规则中使用的文件将具有index: 0
,下一个文件将具有index: 1
,依此类推。我们需要扫描导入文件中的@import
规则以正确计算这些索引。
我们还需要以不同的方式计算absoluteIndex
。与 Microthemer 文件夹不同,系统文件可以包含任意数量的选择器。compileParts
数组可能需要是一个对象,为每个文件存储一个部件数组。这样,我们只需要跟踪给定文件中的选择器索引,然后按文件顺序连接compileParts
数组。
结论
本文介绍了一种新的方式来选择性地编译 Sass 代码。对于 Microthemer 来说,近乎即时的 Sass 编译是必要的,因为它是一个实时 CSS 编辑器。而“实时”这个词带有对速度的某些期望。但这对于其他环境(如 Node.js)也可能是理想的。这取决于 Node.js 和 Sass 用户来决定,并希望他们在下面的评论中分享他们的想法。如果需求存在,我希望 npm 开发人员能够利用我分享的要点。
请随时在我论坛中发布任何关于此的问题。我总是乐于帮助。
你好,顺便说一下 https://webinista.com/updates/dont-use-extend-sass/
嗨,Szymon,
感谢你的评论。是的,我在计划此优化时阅读了许多文章,这些文章不建议使用 @extend。我被这些论点说服了。但是,除非 Sass 弃用此规则,否则选择性编译需要对其进行调整。这很可惜,因为调整 @extend 是应用程序中最繁琐的方面之一!
webinista 文章正确地指出,@extend 的使用从“糟糕的主意”到“接近天才”,取决于实现方式:它是一个棘手的问题,经常被误用。这里似乎并非如此。在测试了 Sebastian 的解决方案后,我没有发现任何问题。
感谢你指出了 webinista 关于 @extend 文章中的细微差别,Noah Quinn。很高兴听到你能够毫无问题地使用我的解决方案。
嘿,Sebastian,这是一个有趣的方法。它将如何处理在任何选择器之外触发的全局 mixin,其功能纯粹是产生副作用?这些 mixin 的例子是“设置”mixin,它们解析创建或转换变量,这些变量是后续代码依赖的……
嗨,Lu,
感谢你的评论,抱歉回复很慢。我的儿子在星期二出生了,我们还在医院接受监护。我应该在下周能够回复得更合适。
谢谢,
Sebastian
再次跟你说声你好,Lu。
我认为这里可能有两个问题。
Q1. Microthemer 如何处理选择器之外的 Sass 内容?
A1. 完整的代码视图允许使用任意 Sass 代码,例如 mixin、函数和变量声明。 那里输入的代码先于添加到单个 UI 选择器的代码。 当前无法在 UI 选择器之间插入任意 Sass 代码片段,不过这将在未来得到支持。
对于 npm 实现,Microthemer 的 UI 文件夹将被替换为常规系统文件(请参阅 npm 的注意事项部分)。 代码分割将在文件级别发生,而不是在单个选择器级别(除非有人知道一种在虚拟上分割系统文件的方法)。 使用常规文件,可以在选择器之间自由插入任意 Sass 代码片段。
Q2. Microthemer 如何处理 mixin 中的副作用?
A2. Microthemer 支持 mixin 和函数的副作用,例如设置全局变量。 我了解到不鼓励在函数中使用副作用,但 MT 支持它们,因为 Sass 支持它们。
在如何操作方面,Microthemer 在解析 Sass 代码时会记录任何副作用。 请注意,之前文章中的 rounded() mixin 具有将 $secondary-color 变量设置为 blue 的副作用。 然后查看 projectEntities 对象 - relatedEntities 键为 secondary_color 和 rounded 属性设置。 然后在 gatherCompileResources() 函数中检查这一点,以拉取任何依赖于 rounded 函数或 $secondary-color 变量的代码。 实际上,Microthemer 将这两个 Sass 实体视为相互依赖的。 因此,总是会编译原始声明,以及任何更新或一起使用它们的代码。
因此,使用您的设置 mixin 示例,如果正在编辑的选择器使用了设置 mixin 变量,则设置 mixin 将包含在选择性编译中。 索引用于确保部分 Sass 代码始终以正确的顺序编译。
这是否回答了您的问题?
如果您想让我详细说明任何内容,请告诉我。
谢谢!
Sebastian
那个 npm 包有什么消息吗?
嗨,感谢你的评论。 我还没有听说过使用这种优化来编译 Sass 的 npm 包……