DataEase 一个隐藏了半年的 Bug:从 Less 内部导入引发的血案

DataEase 一个隐藏了半年的 Bug:从 Less 内部导入引发的血案

一段从未执行过的代码,如何在半年后突然爆炸
一、问题现象
某个普通的下午,DataEase 前端构建突然报错:

Uncaught ReferenceError: Cannot access 'M' before initialization
    at http://localhost:8100/assets/chunk/appearance-873c27cb.js:9:17155

更诡异的是:

✅ 代码运行了整整半年都没问题

✅ 最近没有改动过相关代码

✅ 依赖版本也没有变化

❌ 突然就崩了

二、排查过程
2.1 初期的徒劳尝试
遇到这种"莫名奇妙"的错误,第一反应就是:

# 回滚代码
git checkout HEAD~10

# 重新安装依赖
rm -rf node_modules package-lock.json
npm install

# 升级/降级 Less 版本
npm install less@4.1.3
npm install less@3.13.1

# 修改 Vite 配置
export default defineConfig({
  build: { minify: false },
  optimizeDeps: { exclude: ['less'] }
})

结果:全部无效。

2.2 定位到具体文件
在 index.html 中添加最早期错误捕获:

<script>
  window.onerror = function(message, source, lineno, colno, error) {
    console.log('错误文件:', source);
    console.log('错误堆栈:', error?.stack);
  }
</script>

终于定位到元凶:

错误文件: http://localhost:8100/assets/chunk/appearance-873c27cb.js
错误堆栈: ReferenceError: Cannot access 'M' before initialization
    at appearance-873c27cb.js:9:17155

2.3 找到源头
这个 appearance-xxx.js 对应的是 DataEase 项目中的 appearance.ts 文件。打开一看,发现了问题代码:
在这里插入图片描述

// appearance.ts
import colorFunctions from 'less/lib/less/functions/color.js'  // ⚠️ 危险!
import colorTree from 'less/lib/less/tree/color.js'            // ⚠️ 危险!

// ... 省略其他代码

if (this.themeColor === 'custom' && this.customColor) {
  document.documentElement.style.setProperty(
    '--ed-color-primary-light-5',
    colorFunctions
      .mix(new colorTree('ffffff'), new colorTree(this.customColor.substr(1)), { value: 40 })
      .toRGB()
  )
  // ... 其他颜色处理
}

三、根本原因分析
3.1 什么是 Less 内部模块?
less/lib/less/functions/color.js 和 less/lib/less/tree/color.js 是 Less 编译器源码的内部模块,它们:

❌ 不应该在业务代码中直接导入

❌ 包含复杂的内部依赖和循环引用

❌ 不是公开 API,随时可能变化

3.2 为什么之前没发现?
关键在这段代码:

if (this.themeColor === 'custom' && this.customColor) {
  // 这段代码半年没被执行过!
  // 直到今天用户第一次选择"自定义主题色"
}

3.3 为什么现在暴露了?
用户第一次在 DataEase 系统设置中选择了"自定义主题色":

✅ themeColor === ‘custom’ 成立

✅ customColor 有值

✅ 代码路径第一次被执行

✅ Less 内部模块被实际调用

❌ 循环依赖暴露!

这就是传说中的"休眠 Bug"——代码一直存在,只等特定条件触发。

四、解决方案
4.1 ❌ 错误示范

// 不要这样做!不要直接导入 Less 内部模块!
import colorFunctions from 'less/lib/less/functions/color.js'
import colorTree from 'less/lib/less/tree/color.js'

4.2 ✅ 正确的解决方案
使用专业的颜色处理库 color:

# 安装 color 库
npm install color
npm install @types/color --save-dev

修改后的代码:

// 删除危险导入
// import colorFunctions from 'less/lib/less/functions/color.js'
// import colorTree from 'less/lib/less/tree/color.js'

// 使用专业的 color 库
import Color from 'color'

if (this.themeColor === 'custom' && this.customColor) {
  const baseColor = Color('#' + this.customColor.substr(1))
  
  // 设置基础颜色
  document.documentElement.style.setProperty('--ed-color-primary', this.customColor)
  document.documentElement.style.setProperty('--van-blue', this.customColor)
  
  // light-5: 混合白色 40%
  document.documentElement.style.setProperty(
    '--ed-color-primary-light-5',
    baseColor.mix(Color('#ffffff'), 0.4).hex()
  )
  
  // light-3: 混合白色 15%
  document.documentElement.style.setProperty(
    '--ed-color-primary-light-3',
    baseColor.mix(Color('#ffffff'), 0.15).hex()
  )
  
  // 半透明色
  document.documentElement.style.setProperty('--ed-color-primary-1a', `${this.customColor}1a`)
  document.documentElement.style.setProperty('--ed-color-primary-33', `${this.customColor}33`)
  document.documentElement.style.setProperty('--ed-color-primary-99', `${this.customColor}99`)
  
  // dark-2: 混合黑色 15%
  document.documentElement.style.setProperty(
    '--ed-color-primary-dark-2',
    baseColor.mix(Color('#000000'), 0.15).hex()
  )
}

五、经验总结
5.1 给开发者的建议
🚫 永远不要直接导入库的内部模块

内部模块不是公开 API,随时可能变化

可能包含复杂的循环依赖

应该使用库的官方 API 或专业工具库

📊 代码覆盖率很重要

未执行的代码也可能是定时炸弹

条件分支需要测试覆盖

用户配置路径都要考虑到

🔧 选择合适的工具

颜色处理 → color 库

日期处理 → dayjs 或 date-fns

数值计算 → mathjs

不要"借用"其他库的内部实现

5.2 排查技巧
从错误堆栈定位到具体文件

检查新触发的代码路径

关注条件分支的执行情况

添加早期错误捕获(window.onerror)

5.3 预防措施

// ESLint 规则:禁止导入 less 内部模块
{
  "rules": {
    "no-restricted-imports": ["error", {
      "patterns": ["less/lib/less/*"]
    }]
  }
}

六、结语
这个 Bug 教会我们:

“代码写对了不代表没问题,没执行过的代码永远有风险。”

它不是一个"新" Bug,而是一直潜伏在 DataEase 的代码里,等待被触发的那一天。半年来它都安静地睡着,直到用户第一次选择了自定义主题色,它才终于醒来。

排查这类问题的过程很痛苦,但解决后的成就感也是无可替代的。希望这篇文章能帮到同样踩坑的你。
如需沟通:lita2lz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值