webview白屏的问题查找和修复
介绍
有运营反应,有部分用户访问我们的小程序后,打开 webview
页面,显示白屏,删除小程序重新进入也未解决问题。
初步判断
这里有俩个关键点
仅部分用户出现白屏
以前是能正常访问
初步判断是代码执行的兼容性问题
顺藤摸瓜
刚巧团队内部有个同事的 iphone 7
能复现这个问题, 于是我们首先进行了 charles
抓包, 分别拿到了正常手机和白屏手机的请求, 对比发现俩者从服务端拿到的资源请求包括文件名 hash
是一致的。
到了这里我们基本可以确定不同设备上拿到的代码是一致的,出问题的是兼容性,在不同机型上渲染执行可能出错导致的白屏。
前面提到用户以前是能正常访问 webview
的,也就是在某一段时间我们的代码编译后导致的白屏.
于是我接下来的重点放到了查找问题代码,通过切换月份分支,从 5,6,7
月分别调试缩小到7月范围,然后按照 commit
减半调试缩小范围,确定了包含问题代码的 commit
记录
在多次注释更新的包后,最终确定了问题定位出在 package.json
升级的新包 @muyi086/var-type
上.
出问题的代码分析
这是老版本的代码,使用
js
实现::: code-group
/** * @Description: js变量类型判断 * @Author: MuYi086 * @Email: 1258947325@qq.com * @Blog: https://github.com/MuYi086/blog * @Date: 2021/04/11 12:30 */ class VarType { constructor () { this.typeList = ['Null', 'Undefined', 'Object', 'Array', 'ArrayBuffer', 'String', 'Number', 'Boolean', 'Function', 'RegExp', 'Date', 'FormData', 'File', 'Blob', 'URLSearchParams', 'Set', 'WeakSet', 'Map', 'WeakMap'] this.init() } /** * 判断变量类型 * @param {string} value * @returns lowercase string */ type (value) { const s = Object.prototype.toString.call(value) return s.match(/\[object (.*?)\]/)[1].toLowerCase() } /** * 增加判断类型数据方法 */ init () { this.typeList.forEach((t) => { this['is' + t] = (o) => { return this.type(o) === t.toLowerCase() } }) } /** * isBuffer * @param {any} val * @returns boolean */ isBuffer (val) { return val !== null && !this.isUndefined(val) && val.constructor !== null && !this.isUndefined(val.constructor) && this.isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val) } /** * isStream * @param {any} val * @returns boolean */ isStream (val) { return this.isObject(val) && this.isFunction(val.pipe) } } // 使用 varType["isNull"](null)等 module.exports = new VarType()
var e = class { constructor() { this.typeList = ["Null", "Undefined", "Object", "Array", "ArrayBuffer", "String", "Number", "Boolean", "Function", "RegExp", "Date", "FormData", "File", "Blob", "URLSearchParams", "Set", "WeakSet", "Map", "WeakMap"], this.init() } type(t) { return Object.prototype.toString.call(t).match(/\[object (.*?)\]/)[1].toLowerCase() } init() { this.typeList.forEach(t = >{ this["is" + t] = r = >this.type(r) === t.toLowerCase() }) } isBuffer(t) { return t !== null && !this.isUndefined(t) && t.constructor !== null && !this.isUndefined(t.constructor) && this.isFunction(t.constructor.isBuffer) && t.constructor.isBuffer(t) } isStream(t) { return this.isObject(t) && this.isFunction(t.pipe) } }; module.exports = new e;
:::
这是升级后新版本的代码,使用
ts
实现::: code-group
/** * @Description: js变量类型判断 * @Author: MuYi086 * @Email: 1258947325@qq.com * @Blog: https://github.com/MuYi086/blog * @Date: 2021/04/11 12:30 */ class VarType { private typeList: string[] private static _instance: VarType | null = null constructor() { this.typeList = ['Null', 'Undefined', 'Object', 'Array', 'ArrayBuffer', 'String', 'Number', 'Boolean', 'Function', 'RegExp', 'Date', 'FormData', 'File', 'Blob', 'URLSearchParams', 'Set', 'WeakSet', 'Map', 'WeakMap'] this.init() } static get instance(): VarType { if (!VarType._instance) { VarType._instance = new VarType() } return VarType._instance } /** * 判断变量类型 * @param {string} value * @returns lowercase string */ private type (value: any): string { const s = Object.prototype.toString.call(value) return s.match(/\[object (.*?)\]/)[1].toLowerCase() } /** * 增加判断类型数据方法 */ private init(): void { const self = this this.typeList.forEach((t: string) => { Object.defineProperty(VarType.prototype, `is${t}`, { value: function (o: any) { return self.type(o) === t.toLowerCase() }, writable: true, configurable: true }) }) } /** * isBuffer * @param {any} val * @returns boolean */ static isBuffer(val: any): boolean { return val !== null && (VarType as any).isUndefined(val) && val.constructor !== null && (VarType as any).isUndefined(val.constructor) && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val) } /** * isStream * @param {any} val * @returns boolean */ static isStream(val: any): boolean { return (VarType as any).isObject(val) && (VarType as any).isFunction(val.pipe) } } // 使用 varType["isNull"](null)等 export const varType = VarType.instance
var s = Object.defineProperty; var o = Object.getOwnPropertyDescriptor; var c = Object.getOwnPropertyNames; var u = Object.prototype.hasOwnProperty; var p = (e, t) = >{ for (var n in t) s(e, n, { get: t[n], enumerable: !0 }) }, y = (e, t, n, i) = >{ if (t && typeof t == "object" || typeof t == "function") for (let r of c(t)) ! u.call(e, r) && r !== n && s(e, r, { get: () = >t[r], enumerable: !(i = o(t, r)) || i.enumerable }); return e }; var f = e = >y(s({}, "__esModule", { value: !0 }), e); var b = {}; p(b, { varType: () = >l }); module.exports = f(b); var a = class e { typeList; static _instance = null; constructor() { this.typeList = ["Null", "Undefined", "Object", "Array", "ArrayBuffer", "String", "Number", "Boolean", "Function", "RegExp", "Date", "FormData", "File", "Blob", "URLSearchParams", "Set", "WeakSet", "Map", "WeakMap"], this.init() } static get instance() { return e._instance || (e._instance = new e), e._instance } type(t) { return Object.prototype.toString.call(t).match(/\[object (.*?)\]/)[1].toLowerCase() } init() { let t = this; this.typeList.forEach(n = >{ Object.defineProperty(e.prototype, `is$ { n }`, { value: function(i) { return t.type(i) === n.toLowerCase() }, writable: !0, configurable: !0 }) }) } static isBuffer(t) { return t !== null && e.isUndefined(t) && t.constructor !== null && e.isUndefined(t.constructor) && typeof t.constructor.isBuffer == "function" && t.constructor.isBuffer(t) } static isStream(t) { return e.isObject(t) && e.isFunction(t.pipe) } }, l = a.instance;
:::
对比发现 ts
的代码实现使用了单例模式,并且内部使用了defineProperty
, getOwnPropertyDescriptor
等属性,这也是导致某些机型上不执行代码导致白屏的原因, 因为在编译时没有提供 polyfill
支持旧版浏览器。
解决方案
由于线上 bug
需要紧急修复,所以当前先简单处理,发布一个新的 npm
包 js
版本替换原先的 ts
版本, 然后紧急发版。以后有时间会优化代码,在 ts
版本上尝试修复编译导致的问题。
Last updated