closertb / closertb.github.io

浏览issue 或 我的网站,即可查看我的所有博客
https://closertb.site
32 stars 0 forks source link

2023, 我跨过的技术沟壑 #102

Open closertb opened 8 months ago

closertb commented 8 months ago

角色的转变

在做商品前端之前,我是本地生活前端基础设施WAP平台的全栈开发者。我对接的客户是本地生活的前端,区别于业务前端的是,自己即是产品、也是开发、测试、运维。

商品前端作为业务开发,其工作更细,更强调团队,除了要和产品、服务端、测试密切配合;还需要关注上下游链路、依赖更多,制约更多。

而除了上述差别,还有就是更关注用户体验、关注客诉、关注线上问题。以下便是我2023年遇到的奇葩、棘手,让人个人成长但不多的业务问题集锦top4, 分享与各位共勉:

一个表情符造成的崩溃

某天要下班提包走的瞬间,突然产品拉群,运维反馈说XX商户套餐添加菜品操作页面白屏(其实是一致loading,页面假死),并发了个视频。 20240218230336

发错了,是下面这个 演示

我们迅速根据商家信息去查了接口调用日志,发现请求正常,响应正常未超时,前端系统日志也正常,未上报任何报错信息。

几个人对视频又观察分析,发现商户小程序选菜页面正常,只是H5应用选菜页面打开才会白屏(本质上是整个app崩溃了,页面都没法退出)。然后又和运维交流了一下,说商户安卓手机能正常操作,只有苹果手机有问题(型号:IP15 PRO)。我们测试迅速在我们的测试商户复现,未成功复现。于是又联系运维,要了商户的临时登录帮助复现,也未复现(复现的机型是IP 13)。这就更蒙了,难道只有苹果高端机才有问题?

然后找到一位土豪同事,用他刚上手的水果试了一下,果然复现了,难道这真的是富贵病??? 20240218224959

但机智的我突然开窍,会不会是这个分类表情符造成的?我们迅速在测试账号复现了该分类,并成功验证这个猜想,确实是分类表情符被截断造成。当晚我们给商户的建议是:改一下表情符出现的位置,或者减少数量。

第二天,另一个测试在一台IP 13也复现了,但APP版本一样,唯一不一样的,是系统的版本,他的更新。然后我们又升级了一台,果然是系统升级造成。至于为什么小程序没问题,因为小程序用的是UC内核容器,而H5用的是原生webview容器,即safari内核。

这难道了我,以前只知道去github给仓库作者提issue,这给苹果系统提issue,还是大姑娘上轿-头一回。

20240218230040

在google了苹果bug反馈机制后,迅速写了一个demo,并提了一个issue,一周后,打开测试机看到一条系统更新推送,看到了是关于safari的,果断升级,发现问题已解。

UC内核打造的出其不意

某天bug写的起劲,钉钉消息不断弹出,大事不妙,果然,前线运维反馈APP菜品列表滑动卡顿,并录了视频。 g

习惯性打开测试商户看了下,800个菜,滑动正常,问了下对面,只有100来个菜,这不应该啊。问了对方机型,华为Mate60(没听错,就是那个遥遥领先),那就更不可能啊。毕竟我手上这个vivo老年机,不能说很流畅,但真的不至于卡。又找了几个手机(包括一个Mate50),运行都很正常,然后、然后、就挂起了。

然后隔了几天,另一个同事查问题,发现在一加手机上也很卡。但奇怪的是,出问题当天,我们就用过这手机,测试出来很流畅,而且是两个人一起测过。很快我们发现了不同端,APP由于有发版,测试版自动升级成了正式版。然后我们又找了两台测试机,装了app正式包,发现都有卡顿问题。于是我们把问题抛给了APP。

后面APP不断本地回滚版本测试,发现一年前的版本运行起来都卡。然后解释之所以正式包卡,测试包不卡,是因为UC内核不一样,正式版的内核有优化(这算哪门子的优化)。然后我们就只有从自身找原因,通过代码不断回滚测试,发现9月前的版本运行流畅,9月后的版本就不行了,然后同事发现是因为在虚拟列表的列表单元组件deriveDataFromProps增加了动态计算工作量,当然,这个bug并不是这一个同事造成的,他只是把坑挖的更明显(后面,我们发现,这个迭代,另一个同事正在这个坑里刨,上线后,估计就是P5故障了)。 20240220225232

因为快速滑动,虚拟列表不断地卸载挂载,照成大量的计算与setState,从而导致卡顿。其实更好的做法,是在数据提前计算好传给列表单元,而不是挂载时动态计算。调整后,卡顿问题迅速得到了解决,上线后,我们也收到了运维的正向反馈:

b

这一次问题处理,我意识到了百分百还原问题现场的重要性,不要以我以为的思想来判定问题。

偶现的bug,奔溃的调试

2023财年,最硬核输出可能就是wa-form了,这是KPI下硬挤出来的产物(两个憨憨熬夜一周),但不是废物,因为它确实解决了我们B端场景的联动校验问题,没有Formily那么多概念,但却有formily 70%的能力(大概)。

这个库在第一版时,直接在我们扩品类的业务项目上落地,出现了很多bug。但有一个bug,至今也没找到原因,大概的现象就是: 当我们在这个表单上,持续的触发联动校验,会突然的出现联动校验假死(和操作时间无关,没法稳定复现);但更神奇的是,只会在一个测试机(水果13)出现,安卓机,其他人的苹果都不能复现,开发模式也不能。

但更神奇的事情是,出现这个bug连上safari开始debug时,联动校验又恢复了。所以这神秘的面纱,我们至今没有揭开。

当时我们也是经过评估,认为这个bug线上出现概率不大,确认可以不解决直接上线。

分包也能分出线上问题

一说起webpack的构建拆包,你可能马上就想到要用SplitChunk插件加个配置,这玩意多简单,最多分出个样式问题(由于加载时序造成的同权重样式优先级变化)。但我今年遇到了神奇的,还不止一次,我拆包后,发现大多数页面能运行,但部分页面的某个功能一点,页面就白屏了,一检查,还是那熟悉的味道:Cannot read properties of undefined

更神奇的是,这种bug只会在生产模式出现,开发模式是不能复现的。

在做移动端的拆包优化时,遇到过这问题,也是临近上线才发现,当时时间紧没研究问题,后面就紧急把配置回滚。但今年老板紧追页面加载体验问题,这PC端一个页面4.2M的包,不做拆包就只有贱指325了。是时候动一下手指,加一波配置,原以为经过九九八十一难后,这次一定会成功,但在内灰时,赶巧被测试发现,点某个小功能又白屏了,啥也不说,马上配置回滚,紧急再重新内灰。

临近财年底,手上业务没那么重,就认真研究了下,这不,有点效果!!!放假前给webpack官方提了一个Issue, 让他过不好年(大意了,别人不是中国人), 但过了一天,那熟悉的味道又来了,大概意思:同学你好,你的问题我收到了,你能提供一个最小单元的复现示例吗

20240215231254

满怀信心去新建一个项目,以为能复现,但事实证明,这可能不是官方的bug。但更残酷的事实是:过不好年的,可能是我

但这个示例还是有用的,经过将示例项目和业务项目构建结果对比,发现业务项目会多出这样一段结果:

/******/    /* webpack/runtime/runtimeId */
/******/    (() => {
/******/        __webpack_require__.j = 335;
/******/    })();

经过大年初一初二初三不断抽时间看源码,发现了蛛丝马迹:

  1. webpack/runtime/runtimeId的webpack钩子定义在
    
    // webpack/lib/RuntimePlugin.js
    compilation.hooks.runtimeRequirementInTree
    .for(RuntimeGlobals.runtimeId)
    .tap("RuntimePlugin", chunk => {
    compilation.addRuntimeModule(chunk, new RuntimeIdRuntimeModule());
    return true;
    });

// RuntimeIdRuntimeModule 定义, webpack/lib/runtime/RuntimeIdRuntimeModule.js class RuntimeIdRuntimeModule extends RuntimeModule { constructor() { super("runtimeId"); }

/**
 * @returns {string} runtime code
 */
generate() {
    const { chunkGraph, chunk } = this;
    const runtime = chunk.runtime;
    if (typeof runtime !== "string")
        throw new Error("RuntimeIdRuntimeModule must be in a single runtime");
    const id = chunkGraph.getRuntimeId(runtime);
    return `${RuntimeGlobals.runtimeId} = ${JSON.stringify(id)};`;
}

}


2. 所以要触发钩子调用,就得RuntimeGlobals.runtimeId变化,触发条件
```js
    runtimeConditionExpression({
        chunkGraph,
        runtimeCondition,
        runtime,
        runtimeRequirements
    }) {
        if (runtimeCondition === undefined) return "true";
        if (typeof runtimeCondition === "boolean") return `${runtimeCondition}`;

        /** @type {Set<string>} */
        const positiveRuntimeIds = new Set();
        forEachRuntime(runtimeCondition, runtime =>
            positiveRuntimeIds.add(`${chunkGraph.getRuntimeId(runtime)}`)
        );
        /** @type {Set<string>} */
        const negativeRuntimeIds = new Set();
        forEachRuntime(subtractRuntime(runtime, runtimeCondition), runtime =>
            negativeRuntimeIds.add(`${chunkGraph.getRuntimeId(runtime)}`)
        );
    // 这里有新增RuntimeGlobals.runtimeId 
        runtimeRequirements.add(RuntimeGlobals.runtimeId);
        return compileBooleanMatcher.fromLists(
            Array.from(positiveRuntimeIds),
            Array.from(negativeRuntimeIds)
        )(RuntimeGlobals.runtimeId);
    }

所以,只有在 runtimeCondition 存在且不为boolean时,才会触发RuntimeGlobals.runtimeId添加。

boolean为true时,代表所有chunk都需要;

boolean为false时,代表所有chunk都不需要,可以shaking掉;

  1. 至于什么情况会导致runtimeCondition 存在且不为boolean,这个逻辑非常冗长,简单说下我这边的原因:
    • 因为我们业务的双端组件库,在package.json 配置时,错误的配置了sideEffects(至于为什么会这么配,应该是有意为之,index.js中确实有一段副作用代码,但可能没想到有这么大坑)
      {
      "sideEffects": [
      "es/**/style/*",
      "es/**/*.less",
      "es/theme/*",
      "es/index.js",
      "lib/**/style/*",
      "lib/**/*.less",
      "lib/theme/*",
      "lib/index.js"
      ],
      }
    • 然后在正式构建时,webpack默认开启了构建优化,所以个别文件构建由extermodule转成了concentratedModule(故只有正式包才能复现)
    • 不同的入口对组件的引用方式(直接引用和间接引用),导致index.js中的导入有运行时条件

最直接的解决策略就是改库的sideEffects配置,因为这个库的index.js有一段关于样式的副作用代码,所以调整还包括了库整体架构的调整。终于,终于,在大年初五,我开始心无旁骛的过年了。

你以为这就完了,并没有,webpack针对于大项目的构建确实是有bug的,只能说未完待续(issue 还在持续掰扯)。

新年愿望

但愿2024 不要再有这么多奇奇怪怪的线上问题,毕竟我已早过而立之年,再不学学大模型、AIGC,就真的要面临提前退休,回家种地的境地了。

🙏🏻🙏🏻🙏🏻!!!