UNI-APP相关笔记 - `Uncaught TypeError: t.$el.prepend is not a function`

200318 - Uncaught TypeError: t.$el.prepend is not a function

问题场景还原

  1. index.vue 里使用 <picker>来选取日期

  2. 在Chrome 44 里测试

  3. 显示picker之后关闭它

  4. 此时console里就出现了这个错误.

  5. 发生错误的文件为:webpack:///./node_modules/@dcloudio/uni-h5/dist/index.umd.min.js?1c31

  6. 发生错误的代码行:

    _close: function() {
    var t = this;
    this.visible = !1,
    setTimeout((function() {
    var e = t.$refs.picker;
    e.remove(),
    t.$el.prepend(e),
    e.style.display = "none"
    }
    ), 260)
    }
    

解决方案

  1. 从使用体验上来说,没有受到肉眼可见的影响,所以理论上可以暂不处理.

实验方案6 - 使用最新版本的@Babel来转换试试

  1. 从源码HBuilderX\Bin\plugins\uniapp-cli\node_modules\@dcloudio\vue-cli-plugin-uni\lib\h5\index.js得知

    useBuiltIns === ‘entry‘ ? `import ‘@babel/polyfill‘

  2. @babel/polyfill · Babel 中文网 得知

    polyfill = import "core-js/stable";+import "regenerator-runtime/runtime";

    最新版本查询地址: babel/packages/babel-polyfill/package.json

  3. 从项目运行时调试可知引用的类型为@babel/core-js

    查询`"HBuilderX\Bin\plugins\uniapp-cli\package.json"`可知当前版本为
    "core-js": {
      "version": "2.6.1",
    
  4. 在线搜索 zloirock/core-js: Standard Library 关键字prepend

    没有任何匹配结果

  5. 结论: @babel/core-js无论新3旧0版本都不包含prepend的转换

    可能是因为它是实验性特性功能?

实验方案5 - 修改全局的的uniapp-clibrowserslist兼容性配置

  1. Can I use - prepend

    • Android 4.4.4 不支持
    • Chrome 53 不支持
    • 实验性功能
  2. "HBuilderX\Bin\plugins\uniapp-cli\package.json"

    参考: [解决方案5:[√有效的 最终采用的方案]](#解决方案5:[√有效的 最终采用的方案])

    "browserslist": [
    "Chrome >= 53"
    ]
    "browserslist": [
    "Android >= 4"
    ]
    
  3. 无论怎么修改都无法解决问题.

  4. 大胆假设:难道是@babel版本太老的问题? 结论:不是的.

实验方案4 - 研究@financial-times/js-features-analyser源码寻找原因

  1. 在[实验方案3](#实验方案3 - 研究Polyfill.io - create-polyfill-service-url源码寻找原因) 基础上一步步追溯源头发现是入口文件里在调用generatePolyfillURL 关键函数

    async function generatePolyfillURL(features = [], supportedBrowsers = [])

    "node_modules\create-polyfill-service-url\index.js"

  2. 然后关键features 参数内容由@financial-times/js-features-analyser组件提供

    Create a bundle which targets the syntax level you support (ES3/5/2017/2019) and then pass that bundle through this tool

  3. 在其源码目录可见其支持的各种类型

    node_modules\create-polyfill-serv ice-url\node_modules\@financial-times\js-features-analyser\src

    • built-in-definitions.js
    • features.js
    • globals.js
    • instanceMethods.js
    • staticMethods.js
  4. 结论就是这个组件根本不支持检测prepend关键字

  5. 灵光一闪:是否可以通过polyfill-library获取所有支持的特性,来代替js-features-analyser里的特性

    node_modules\create-polyfill-service-url\node_modules\polyfill-library\polyfills\__dist\Element.prototype.prepend

    {
    "aliases": ["default-3.4", "default-3.5", "default-3.6", "default"],
    "dependencies": ["document", "Element", "_mutation"],
    "spec": "http://dom.spec.whatwg.org/#dom-parentnode-prepend",
    "browsers": {
    "android": "<5",
    "bb": "*",
    "chrome": "<54",
    "edge": "<17",
    "edge_mob": "<17",
    "firefox": "<49",
    "ios_chr": "*",
    "ios_saf": "<10",
    "ie": "6 - *",
    "ie_mob": "10 - *",
    "opera": "<39",
    "op_mini": "*",
    "safari": "<10",
    "firefox_mob": "<49",
    "samsung_mob": "<6"
    },
    "detectSource": "\"Element\"in self&&\"prepend\"in Element.prototype\n",
    "baseDir": "Element/prototype/prepend",
    "hasTests": true,
    "isTestable": true,
    "isPublic": true,
    "size": 123
    }
    

    思路:

    1. 获取所有polyfill-library支持的特性

    2. 想办法检测"项目源码"里是否有用到上面的特性

    • 办法1: 参考js-features-analyser的方式

    优点: 项目用到啥就检测啥
    缺点: 怕检测不准确

    • 办法2: 读取所有特性配置文件里的browsers,然后根据项目配置的浏览器兼容性来筛选需要用到的所有特性集合

    优点: 一次性将最低版本不兼容性的特性全弄出来.

    缺点: 返回的数据可能有些大

    1. 然后拼接成URL

    2. 然后自己部署polyfill-service - npm 在线服务

    防止官方CDN网址失效

    1. 在@Babel编译后的基础上再检测,会更加的精简.

实验方案3 - 研究Polyfill.io - create-polyfill-service-url源码寻找原因

  1. 进入其所在目录找到入口文件

    node_modules\create-polyfill-service-url\index.js

  2. 发现代码很短,且用到了browserslist

    const browsersListConfig = browserslist.loadConfig({
    path: process.cwd()
    })
    
  3. 实验方案1的开始前,在all.js脚本所在目录增加package.json

    {
    "browserslist": [
    "Android >= 4",
    "ios >= 8"
    ]
    }
    
  4. 然后再按照实验方案1开始执行结果报错

    android 4.0.0 supports document
    ios_saf 7.0.0 supports document
    android 80.0.0 supports Promise
    android 4.4.3 does not support Promise
    android 80.0.0 does not support WeakSet
    android 80.0.0 supports Set
    android 4.4.3 does not support Set
    Failed to parse the JavaScript from xxx because an error occured:
    TypeError: Cannot read property ‘browsers‘ of undefined

  5. 根据爆出错误的堆栈信息,将源码修复以下

    "\node_modules\create-polyfill-service-url\src\index.js"

    1. generatePolyfillURL 函数里

    async function generatePolyfillURL(features = [], supportedBrowsers = [])

    1. 增加以下代码
    if (supportedBrowsers.length > 0) {
    for (const feature of featuresInPolyfillLibrary) {
    const featureConfig = await polyfillLibrary.describePolyfill(feature);
    +if(!featureConfig) {
    + console.error( "not support the feature: ", feature );
    + continue;
    +}
    const browsersWithoutFeature = featureConfig.browsers;
    
  6. 重新运行就不再报错了.

  7. 结果生成的URL特性里还是没有 没有 检测到prepend关键字!!!

实验方案2 - 结合有效方案1 + 实验方案1

  1. 使用有效方案1的思路

  2. 将URL替换为实验方案1里得到的URL

  3. 实验结果:失败: polyfill.min.js文件加载8秒后失败

  4. 对比分析后发现失败原因是"无法解析cdn.polyfill.io的对应IP地址"

  5. 然后将其修改为polyfill.io再次实验

  6. 最后实验结果:失败

    • 成功的下载到了polyfill.min.js文件
    • 结果关闭Picker时还是爆出同样的异常
  7. 结论就是:Polyfill.io - create-polyfill-service-url 功能没有检测到.

实验方案1 - 使用Polyfill.io - create-polyfill-service-url分析看看

  1. 将发布后的几个.js文件使用uglify-js 合并为一个all.js文件

    此时在该文件里搜索prepend关键字,能正常匹配到.

  2. 使用 Polyfill.io - create-polyfill-service-url 功能检测.JS文件

    "D:\Program Files\Node.JS\node-v12.16.1-win-x64\create-polyfill-service-url.cmd" analyse --file all.js >r.md

  3. 最终得到了哪些函数特性需要转换

    https://cdn.polyfill.io/v3/polyfill.min.js?features=Array.from,Array.of,Array.prototype.copyWithin,Array.prototype.entries,Array.prototype.fill,Array.prototype.find,Array.prototype.findIndex,Array.prototype.includes,Array.prototype.keys,Array.prototype.values,ArrayBuffer,console,CustomEvent,DataView,document,es5,Event,getComputedStyle,JSON,localStorage,Map,Math.acosh,Math.asinh,Math.atanh,Math.expm1,Math.fround,Math.imul,Math.log1p,Math.sign,Math.sinh,modernizr:es6string,MutationObserver,Number.parseFloat,Number.parseInt,Object.assign,Object.getOwnPropertySymbols,Object.is,Object.isExtensible,Object.isFrozen,Object.preventExtensions,Object.setPrototypeOf,Object.values,Promise,Promise.prototype.finally,Reflect,Reflect.defineProperty,Reflect.ownKeys,RegExp.prototype.flags,requestAnimationFrame,Set,setImmediate,String.fromCodePoint,Symbol,Symbol.iterator,Symbol.toStringTag,Uint16Array,Uint8ClampedArray,WeakMap,WeakSet,XMLHttpRequest
    
  4. 结果居然 没有 检测到prepend关键字!!!

有效方案1 - 使用Polyfill.io - url-builder直接选择prepend关键字

  1. 打开Polyfill.io - url-builder 网站 通过筛选prepend关键字将匹配的都勾选上得到以下URL

    https://polyfill.io/v3/polyfill.min.js?features=document%2CElement.prototype.prepend%2CDocumentFragment.prototype.prepend
    
  2. 然后在发布后的index.html文件里chunk-vendors.??.js脚本之前增加以上脚本引用

    <div id=app></div>
    <script src=https://polyfill.io/v3/polyfill.min.js?features=document%2CElement.prototype.prepend%2CDocumentFragment.prototype.prepend></script>
    <script src=/H5/js/chunk-vendors.6a015935.js></script>
    <script src=/H5/js/index.07e4de0f.js></script>
    
  3. 然后在Chrome 44 版本上打开网页 - 选择时间Picker - 再关闭

  4. √成功解决问题.

参考资料

  1. 重新认识caniuse - 知乎

    • browserslist能够把上面近似于人类语言的配置,转换成一组浏览器集合。
    • Browserslist的浏览器数据来源就是这个caniuse-lite,而它是caniuse-db库的精简版本
    • 每当增加一个新特性时,都要对以上浏览器列表以及对应版本列表进行实测,特性的测试可使用以下两个官方推荐的网站https://www.browserstack.comhttp://saucelabs.com
  2. Can I use - prepend

    • Android 4.4.4 不支持
    • Chrome 53 不支持
  3. javascript - Error with method ParentNode.append() transpiling ES6 with Babel & Webpack - Stack Overflow javascript - Error with method ParentNode.append() transpiling ES6 with Babel & Webpack - Stack Overflow

    Babel is a JavaScript compiler. It polyfills the JS language features. But append is a DOM feature so it can‘t be polyfilled by babel. You could use ember-cli-polyfill-io to polyfill the DOM.

    Reference:

    (1) ParentNode.append polyfill is missing

    (2) What babel-polyfill doesn‘t include

    List of missing polyfills:

    • JSON is missing only in IE7
    • String#normalize is missing due to its rare usage and large size. Alternative polyfill: unorm
    • Proxy can‘t be polyfilled
    • window.fetch is not part of ECMAScript, but a web standard. While core-js does include some web standards, fetch is currently not one of them. Alternative polyfill: github fetch
    • Intl is missing because of its large size. Alternative polyfill: Intl.js
    • DOM polyfills. For example element.closest. What is included however are iterable DOM collections

    at this moment DOM polyfills are not in the core-js scope.2017

    • ``. Instead, use bundlers like webpack or rollup
  4. zloirock/core-js: Standard Library

  5. ParentNode.prepend() - Web APIs | MDN

    • You can polyfill the prepend() method if it‘s not available
    • Chrome 54 以上版本
  6. Polyfill.io

    • Element.prototype.prepend

    • DocumentFragment.prototype.prepend

    • Supported Browsers

    1. Internet Explorer 8
    2. Chrome 29
    3. Android 4.3

UNI-APP相关笔记 - `Uncaught TypeError: t.$el.prepend is not a function`

上一篇:微信分享自定义图片标题摘要-微信官方API


下一篇:【韦东山】嵌入式全系统:单片机-linux-Android对硬件操作的不同侧重点