前端项目构建记录 --- Webpack4 + Babel 在 ie8 下的应用

2,838 阅读4分钟

Object.defineproperty

对象不支持此操作

如果,使用了 importrequire 加载代码, 那么 ie8 会提示:

第一反应:

  1. Object.defineProperty 的 polyfill
  2. 说的就是你! core-js

因为是 bootstrap 里执行的, 所以 polyfill 得要比入口文件更早执行。

引发了异常但未捕获Accessors not supported!

如果,额外并提前的引用了 core-js, 那么会提示:

【core-js issue】IE8 and FF3.6.28 parse error for core-js/es5

Looks like you uses Object.defineProperty / Object.defineProperties / Object.create with getters / setters. In ES3 is no way to add accessors, so we generate this error.

因为 ie8 不支持、模拟不了 Object.defineProperty / Object.defineProperties / Object.creategetters / setters 属性, 所以 core-js 会提示上图所示错误。

但是
说是说不能在ie8里实现,这个“实现”是指较完美、完美的实现, 其实个别功能还是可以模拟的。 比如:接受参数、赋值。Object.defineProperty ie8

if (!supportsDescriptors) {
    Object.defineProperty = function (a, b, c) {
        //IE8支持修改元素节点的属性
        if (origDefineProperty && a.nodeType == 1) {
            return origDefineProperty(a, b, c);
        } else {
            a[b] = c.value || (c.get && c.get());
        }
    };
}

其实它模拟的场景很有限, 就这一句 a[b] = c.value || (c.get && c.get());

而正好就是这一句, 它把 __webpack_require__.r 场景里的需求满足了, 所以, 提前 (在core-js前) 引用好上面那个 polyfill, 是没有问题的。

所以:

  1. @babel/plugin-transform-modules-commonjs 换成 commonjs 加载就可以避免这个问题,但是也失去了tree shaking(推荐,都ie8了, tree个锤子tree)
  2. 清楚了 Object.defineProperty polyfill 的原理后, 也可以酌情使用

对象不支持“bind”属性或方法

明明引用了 core-js, 却还提示这个错误
发现错误跟 chunk 有关, 在代码中用了 dynamic import 或者 webpack 配置了 splitChunks 后会把对应的模块单独分出 chunk 文件。
即使在入口文件的第一行引用了 core-js, 但是这个 chunk loading 执行时机显然在入口文件之前。 所以:

  1. core-js 要比入口文件更早执行,就是额外使用 script 引用
  2. 仅仅额外引用一个 bind 的 polyfill mdn Function.bind
  3. 不要触发 chunk
  4. 滚tmd ie8(白日做梦)

无法获取未定义或 null 引用的属性“appendChild”

使用了 dynamic import , 就会有这个问题

import(/* webpackChunkName: 'ie-polyfill'*/ './ie-polyfill')  

ie8 不支持 document.head, 用 document.getElementsByTagName('head')[0] 代替。

所以,加 加 polyfill:

if (document.head === undefined) {
    document.head = document.getElementsByTagName('head')[0]
}

同样的, 这需要提前到入口文件之前

console

ie8/9 没有唤出开发者工具的时候调用 console 会提示错误的, 为了避免这种情况要增加对 console 的检验, 加段 console polyfill 就好了。

对象不支持此操作 console.log(...[arr]) => console.log.apply(console, arr)

解构赋值是 es6 推出的语法糖, 好用也常用。解构赋值会经babel转译成.apply应用。但是对于 ie8/9 下的console来说, console[log|error|...].apply都会提示 对象不支持此操作

console.log.apply not working in IE9-
摘要:
console 对象其实不是 ES 的标准, 它实际上是文档对象模型的扩展(像其它 DOM 对象一样)。
所以,它就不像 ES 的普通对象、函数一样继承 Object,也不会继承 Function 的方法。
IE 9 已经对大部分的 DOM 对象改进成会继承原生 ES 类型了, 但是console控制台被认为是 ie 的扩展, 所以没有进行同样的修改。

所以, 还是加 polyfill

结论

  1. @babel/plugin-transform-modules-commonjs 避免 Object.defineProperty
  2. 额外引用一个 bind 的 polyfill, 因为用npm装库太方便了...
  3. 兼容 document.head
  4. 兼容 console
  5. 整理ie相关 polyfill 打包成单独的文件ie-chore-polyfill.js

实际应用我是这样做的:webpack 设置两个文件 entry: { main: './src/index.js', 'ie-chore-polyfill': './src/ie-chore-polyfill.js' }, 页面先引用 ie-chore-polifill, 再 main.js main 里包含着 core-js

实践的项目地址 pbb
整理的 ie-chore-polyfill.js

ie-chore-polyfill.js

/**
* polyfill 主要以 `core-js`为主,这里辅助一些其它情况,如:帮助在 webpack 构建下运行
* - insureConsole: 兼容找不到 console 对象的情况。未打开 devtools 的时候会因为 console 报错。 因为 console 不是 ES 的标准, 要保证 console 的运行某些环境要检验以保证安全的使用 // https://www.jianshu.com/p/85a4319e3bf4
* - ie9ConsoleDeconstructionAssignment: 兼容 console 没有继承 Function 方法的情况,e.g. console.log.apply(), 常见于 babel 转换后的代码。
* - ie8ImperfectObjectDefineProperty: ie8 下简易的模拟 Object.defineProperty,可满足 __webpack_require__.r
* - ie8DocumentHead: ie8 不支持 document.head
* - ie9FunctionBind: ie9-、node < 0.6 不支持 Function.prototype.bind。 core-js 有, 但是懒得把 core-js 单独提前引用。。。
*/

参考资料