1. @import 语法解析过程
@import
是一个特殊的 CSS 规则,用于导入其他样式表。它的语法支持两种形式:
css
@import "style.css"; /* 直接导入 */
@import url("style.css") screen and (min-width: 768px); /* 带媒体查询的导入 */
引擎在解析时会进行如下处理:
cpp
// third_party/blink/renderer/core/css/parser/css_parser.h
class CSSParser {
private:
// @import 规则的语法解析
CSSImportRule* ParseImportRule(CSSParserTokenRange& range) {
// 1. 必须检查是否在样式表开头
if (!IsImportRuleAllowed()) {
// @import 只能在其他规则之前使用
AddError("@import rules must precede all other rules");
return nullptr;
}
// 2. 解析 URL
String url;
if (range.Peek().GetType() == kStringToken) {
// 处理 "url.css" 形式
url = range.ConsumeString();
} else if (range.Peek().GetFunctionId() == CSSValueID::kUrl) {
// 处理 url("url.css") 形式
url = ConsumeFunctionPayload(range);
}
// 3. 解析 media query(可选)
MediaQuerySet* media_queries = nullptr;
if (!range.AtEnd()) {
media_queries = ParseMediaQueryList(range);
}
return CSSImportRule::Create(url, media_queries);
}
};
2. @import 资源加载与处理
当浏览器遇到 @import
规则时,会执行以下步骤:
- 资源加载 :
- 暂停当前样式表的解析
- 开始加载被导入的样式表
- 等待加载完成才继续处理当前样式表
- 处理流程:
cpp
class StyleSheetContents {
private:
void ProcessStyleSheet() {
// 1. 设置状态为处理导入
processing_state_ = ProcessingState::kProcessingImports;
// 2. 首先处理所有 @import
for (auto* import_rule : import_rules_) {
// 递归处理每个导入的样式表
import_rule->ProcessImportedStylesheet();
// 等待导入完成
if (!import_rule->IsLoaded()) {
// 记录未完成的导入
pending_imports_.push_back(import_rule);
return;
}
}
// 3. 所有 @import 处理完成后,才处理其他规则
processing_state_ = ProcessingState::kProcessingRules;
ProcessRules();
}
};
这种处理方式会导致:
- 串行加载,影响性能
- 阻塞后续规则的处理
- 可能造成加载瀑布流
3. @import 的优先级规则
@import
的优先级规则比较特殊:
-
基础优先级:
@import
导入的样式表优先级是最低的- 它会被同一样式表中的所有直接规则覆盖
css/* theme.css */ .button { color: red; } /* main.css */ @import "theme.css"; .button { color: blue; } /* 这个会覆盖 theme.css 中的规则 */
-
多重导入的优先级:
css/* main.css */ @import "theme1.css"; /* 优先级最低 */ @import "theme2.css"; /* 优先级比 theme1.css 高 */ .button { color: blue; } /* 优先级最高 */
-
优先级处理代码:
cpp
class StyleResolver {
void ApplyMatchedRules() {
// 1. 先应用所有导入的样式(按导入顺序)
for (auto* import : sheet_->ImportRules()) {
ApplyImportedRules(import);
}
// 2. 再应用当前样式表的规则(会覆盖导入的规则)
ApplySheetRules(sheet_);
}
};
4. 为什么要求 @import 必须在前面?
这个设计有两个主要原因:
-
技术原因:
- 确保导入的样式表能被当前样式表的规则覆盖
- 简化优先级处理逻辑
- 避免复杂的规则交织
-
性能原因:
- 让浏览器尽早发现并加载依赖的样式表
- 避免加载顺序混乱
- 减少样式重计算
5. 实际应用示例
css
/* theme.css */
.button {
color: red;
padding: 10px;
}
/* main.css */
@import "theme.css";
@import "layout.css" screen and (min-width: 768px);
.button {
color: blue; /* 会覆盖 theme.css 中的 color */
/* 但不会覆盖 padding */
}
处理顺序:
- 加载并应用
theme.css
中的所有样式 - 如果屏幕宽度 ≥ 768px,加载并应用
layout.css
- 应用
main.css
中的样式,覆盖之前导入的相同规则
这就是为什么:
@import
的样式可以被覆盖- 但它仍然是一个有用的模块化工具
- 适合用于主题和基础样式的引入
5. @import 内部工作机制详解
当浏览器解析 CSS 遇到 @import
规则时,会执行以下步骤:
cpp
class StyleEngine {
void ProcessStyleSheet() {
// 1. 遇到 @import 时
if (token.type == CSSTokenType::Import) {
// 暂停当前样式表的解析
PauseCurrentParsing();
// 开始加载 @import 的样式表
LoadImportedStylesheet();
// 必须等待加载完成才能继续
WaitForImportComplete();
}
}
};
class CSSImportRule {
void LoadImportedStylesheet() {
// 阻塞式加载
auto* imported_content = loader_->LoadStylesheetSync(import_url_);
// 将导入的样式插入到父样式表的开头
parent_sheet_->PrependRules(imported_content->Rules());
// 标记导入完成
SetLoadComplete();
// 通知父样式表继续解析
parent_sheet_->ResumeParsing();
}
};
具体执行流程:
-
解析阻塞:
css/* main.css */ @import "theme.css"; /* 第1步:停止解析,先加载 theme.css */ .button { color: blue; } /* 第3步:等 theme.css 加载完才解析这行 */
-
样式插入:
css/* 最终的处理结果相当于 */ <style> /* theme.css 的内容被插入到最前面 */ .button { color: red; } /* 第2步:theme.css 的内容 */ /* main.css 的原始内容 */ .button { color: blue; } /* 第3步:这里会覆盖上面的规则 */ </style>
-
加载时序:
sqlmain.css 开始加载 | 遇到 @import "theme.css" -----> 暂停 main.css 解析 | 加载 theme.css | theme.css 加载完成 | 继续解析 main.css <------------- | 完成
这种机制确保了:
-
样式优先级的正确性:
- 导入的样式被放置在最前面
- 后面的规则可以正确覆盖导入的样式
- 维护了 CSS 的层叠性(cascade)
-
加载的完整性:
- 确保所有导入的样式都被加载
- 避免样式加载顺序错乱
- 保证样式应用的一致性
-
性能影响:
- 会阻塞后续 CSS 的解析
- 导致串行的加载过程
- 这就是为什么不推荐使用
@import
的主要原因
相比之下,使用多个 <link>
标签可以实现样式表的并行加载:
html
<link rel="stylesheet" href="theme.css" />
<link rel="stylesheet" href="main.css" />
<!-- 这两个样式表会并行加载 -->
这就是为什么在性能优化时,通常建议:
- 避免使用
@import
- 使用多个
<link>
标签 - 如果必须使用
@import
,尽量减少嵌套层级