我不知道的 CSS:企微自建应用的 @layer Bug 🚀

391 字 7 min read
bug css 兼容性 layer 前端开发

背景

在企微自建应用的开发中,我们选用了 @umijs/max + tailwindcss + antd 的技术栈。这套组合在快速开发和组件支持上表现优异,但 Tailwind CSS 和 Ant Design 的样式冲突成了难题。为优雅解决冲突,我们引入了 CSS @layer 指令,通过分层管理样式优先级。配置如下:

// tailwind.css
@layer tailwind-base, antd;

@layer tailwind-base {
    @tailwind base;
}

@tailwind components;
@tailwind utilities;

@layer tailwind-base, antd; 定义了 tailwind-base(Tailwind 基础样式)和 antd(Ant Design 样式)两层,旨在隔离冲突,使样式管理更清晰。

Bug 表现 🐛

然而,在部分 macOS 系统的企微内置浏览器中,页面出现了严重问题:

  • 样式大面积缺失: 元素失去样式,布局错乱,组件失效,页面像“裸奔”一样。
  • 非必现但稳定复现: 并非所有用户遇到,但在特定 macOS 用户中稳定重现,排查难度倍增。

Debug 流程:从现象到根因 🤔

面对这个“薛定谔的 bug”,我们按以下步骤抽丝剥茧:

  1. 现象观察与复现
    用户反馈显示,bug 集中在 macOS 系统的企微内置浏览器,Windows 和 Chrome 下正常。初步测试发现,样式丢失与企微版本无关,指向系统或浏览器差异。

  2. 逐步排查

    • 版本测试: 对比多版本企微客户端,bug 与企微版本无直接关联,排除客户端因素。
    • 环境锁定: 收集 UA 数据,发现 macOS 下企微内置浏览器基于 Safari 内核,且版本与系统 Safari 一致(例如 AppleWebKit/605.1.15)。
    • 兼容性验证: 查看 Can I Use,确认 @layer 在 Safari 15.4 beta(macOS 12.4)前支持不完善,老版本(如 15.3)可能忽略 @layer 规则。
  3. 根因分析
    真相浮出水面:企微在 macOS 下默认使用 Safari 内核渲染内置浏览器,部分用户 macOS 未更新,Safari 版本低于 15.4,导致 @layer 解析失败,样式未应用。这是 bug 的核心原因。

  4. 解决方案与验证
    为兼容老版本 Safari,我们引入 postcss-preset-env 插件,在 Umi 中配置如下:

    // config/config.ts
    import {defineConfig} from '@umijs/max';
    const postcssPresetEnv = require('postcss-preset-env');
    
    export default defineConfig({
        extraPostCSSPlugins: [postcssPresetEnv()]
    });
    

    该插件将 @layer 转换为老浏览器可识别的 CSS 语法。重新构建后,在问题环境测试,样式恢复正常,bug 解决 🎉。

技术之外的思考 💡

用户体验

  • 现状: 依赖用户反馈发现 bug,响应滞后。

  • 改进: 使用 Sentry 监控 CSS 异常,具体实践如下:

    import * as Sentry from '@sentry/browser';
    
    Sentry.init({dsn: 'your-dsn'});
    
    // 监控 CSS 文件加载失败
    document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
        link.onerror = () => {
            Sentry.captureMessage(`CSS 加载失败: ${link.href}`, 'error');
        };
    });
    
    // 检测关键元素样式是否符合预期
    window.addEventListener('load', () => {
        const el = document.querySelector('.key-element');
        if (!el) {
            Sentry.captureMessage('关键元素未找到', 'error');
            return;
        }
    
        const style = window.getComputedStyle(el);
        const expectedWidth = '128px'; // 假设 w-32 = 8rem = 128px
        const isWidthMissing = style.width !== expectedWidth && (style.width === '0px' || !style.width);
        const isDisplayHidden = style.display === 'none' || style.visibility === 'hidden';
    
        if (isWidthMissing && !isDisplayHidden) {
            Sentry.captureMessage(
                `关键元素样式异常,预期宽度 ${expectedWidth},实际 ${style.width}`,
                'warning'
            );
        }
    });
    
  • 效果: 主动捕获 @layer 失效导致的样式异常,可复用于其他 CSS 兼容性问题。

项目管理:加强跨浏览器测试

  • 现状: 测试不足,上线后暴露问题。
  • 改进: 搭建自建云测平台(基于 f2etest),覆盖 macOS(Safari 15.3+)、Windows(Edge、Chrome)。CI/CD 集成:
    # .github/workflows/test.yml
    steps:
        - name: Run f2etest Cloud Tests
          run: node f2etest.js --browsers "safari@15.3,chrome@120"
    
  • 效果: 成本低、可定制,上线前验证兼容性。

团队协作:整理 CR 关注点

  • 现状: 缺乏兼容性检查,CR 流于形式。
  • 改进: 整理 CR 关注点文档:
    • 新 CSS 特性需说明兼容性(如 @layer Safari < 15.4 不支持)。
    • 工具配置需注释用途(如 PostCSS 解决兼容性)。
    • 通过 GitHub PR 分析,监控评论率(目标:每 PR 至少 2 条有效评论)。
    • 示例:
      /* @layer 隔离样式,Safari < 15.4 不支持,依赖 postcss-preset-env */
      @layer tailwind-base, antd;
      
  • 效果: 提高 CR 质量,确保协作有效。

实用建议 💪

  1. 查询兼容性脚本
    快速检查特性支持:

    npx caniuse @layer --browsers "safari >= 15, chrome >= 99"
    
  2. 配置 Browserslist
    package.json 指定目标浏览器:

    "browserslist": [
        "> 0.5%",
        "last 2 versions",
        "Safari >= 15"
    ]
    

    注意: 若运行在低于指定版本的浏览器(如 Safari 14),PostCSS 不会生成兼容代码,@layer 可能失效,样式缺失。

  3. 调试技巧:特征检测与云测

    • 特征检测: 检查 @layer 支持:
      if (!CSS.supports('@layer')) {
          console.warn('浏览器不支持 @layer');
      }
      
    • 云测验证: 使用 f2etest 在真实设备上测试,避免 UA 伪装局限性。

总结

@layer bug 的排查到解决,我们不仅修复了技术问题,还优化了用户体验、项目流程和团队协作。希望这些经验和工具助你在前端开发中少踩坑,项目更稳定 🚀。