@import 样式工作原理详解

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 规则时,会执行以下步骤:

  1. 资源加载
    • 暂停当前样式表的解析
    • 开始加载被导入的样式表
    • 等待加载完成才继续处理当前样式表
  2. 处理流程
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 的优先级规则比较特殊:

  1. 基础优先级

    • @import 导入的样式表优先级是最低的
    • 它会被同一样式表中的所有直接规则覆盖
    css 复制代码
    /* theme.css */
    .button {
      color: red;
    }
    
    /* main.css */
    @import "theme.css";
    .button {
      color: blue;
    } /* 这个会覆盖 theme.css 中的规则 */
  2. 多重导入的优先级

    css 复制代码
    /* main.css */
    @import "theme1.css"; /* 优先级最低 */
    @import "theme2.css"; /* 优先级比 theme1.css 高 */
    .button {
      color: blue;
    } /* 优先级最高 */
  3. 优先级处理代码

cpp 复制代码
class StyleResolver {
    void ApplyMatchedRules() {
        // 1. 先应用所有导入的样式(按导入顺序)
        for (auto* import : sheet_->ImportRules()) {
            ApplyImportedRules(import);
        }

        // 2. 再应用当前样式表的规则(会覆盖导入的规则)
        ApplySheetRules(sheet_);
    }
};

4. 为什么要求 @import 必须在前面?

这个设计有两个主要原因:

  1. 技术原因

    • 确保导入的样式表能被当前样式表的规则覆盖
    • 简化优先级处理逻辑
    • 避免复杂的规则交织
  2. 性能原因

    • 让浏览器尽早发现并加载依赖的样式表
    • 避免加载顺序混乱
    • 减少样式重计算

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 */
}

处理顺序:

  1. 加载并应用 theme.css 中的所有样式
  2. 如果屏幕宽度 ≥ 768px,加载并应用 layout.css
  3. 应用 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();
    }
};

具体执行流程:

  1. 解析阻塞

    css 复制代码
    /* main.css */
    @import "theme.css"; /* 第1步:停止解析,先加载 theme.css */
    .button {
      color: blue;
    } /* 第3步:等 theme.css 加载完才解析这行 */
  2. 样式插入

    css 复制代码
    /* 最终的处理结果相当于 */
    <style>
      /* theme.css 的内容被插入到最前面 */
      .button { color: red; }  /* 第2步:theme.css 的内容 */
    
      /* main.css 的原始内容 */
      .button { color: blue; } /* 第3步:这里会覆盖上面的规则 */
    </style>
  3. 加载时序

    sql 复制代码
    main.css 开始加载
    |
    遇到 @import "theme.css" -----> 暂停 main.css 解析
                                   |
                                   加载 theme.css
                                   |
                                   theme.css 加载完成
                                   |
    继续解析 main.css <-------------
    |
    完成

这种机制确保了:

  1. 样式优先级的正确性

    • 导入的样式被放置在最前面
    • 后面的规则可以正确覆盖导入的样式
    • 维护了 CSS 的层叠性(cascade)
  2. 加载的完整性

    • 确保所有导入的样式都被加载
    • 避免样式加载顺序错乱
    • 保证样式应用的一致性
  3. 性能影响

    • 会阻塞后续 CSS 的解析
    • 导致串行的加载过程
    • 这就是为什么不推荐使用 @import 的主要原因

相比之下,使用多个 <link> 标签可以实现样式表的并行加载:

html 复制代码
<link rel="stylesheet" href="theme.css" />
<link rel="stylesheet" href="main.css" />
<!-- 这两个样式表会并行加载 -->

这就是为什么在性能优化时,通常建议:

  • 避免使用 @import
  • 使用多个 <link> 标签
  • 如果必须使用 @import,尽量减少嵌套层级
相关推荐
pe7er7 分钟前
使用CDN、ImportMap增强Vue playground
前端
ze_juejin17 分钟前
Angular的懒加载由浅入深
前端
JSON_L17 分钟前
Vue 详情模块 4
前端·javascript·vue.js
码间舞24 分钟前
什么是Tearing?为什么React的并发渲染可能会有Tearing?
前端·react.js
gnip35 分钟前
做个交通信号灯特效
前端·javascript
小小小小宇36 分钟前
Webpack optimization
前端
尝尝你的优乐美38 分钟前
前端查缺补漏系列(二)JS数组及其扩展
前端·javascript·面试
咕噜签名分发可爱多40 分钟前
苹果iOS应用ipa文件安装之前?为什么需要签名?不签名能用么?
前端
她说人狗殊途1 小时前
Ajax笔记
前端·笔记·ajax
yqcoder1 小时前
33. css 如何实现一条 0.5 像素的线
前端·css