热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

精通前端polyfill,兼容各浏览器运行E6语法

ES6在2015正式发布已经多年。最新浏览器们逼近100%的支持率,但为了少数用户体验,我们很可能需要兼容IE9。Babel默认只转码ES6的新语法&#

ES6 在2015正式发布已经多年。最新浏览器们逼近100% 的支持率,但为了少数用户体验,我们很可能需要兼容IE9。 Babel 默认只转码 ES6 的新语法(syntax),而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign、Array.from)都不会转码,这时我们就需要提供polyfill。

babel 和 polyfill

刚接触 babel 的同学可能都认为在使用了 babel 后就可以无痛的使用 ES6 了,之后被各种 undefined 的报错无情打脸。一句话概括, babel 的编译不会做 polyfill。那么 polyfill 是指什么呢? 翻译: 一种用于衣物、床具等的填充材料

const foo = (a, b) => {return Object.assign(a, b);
};

当我们写出上面这样的代码,交给 babel 编译时,我们得到了:

"use strict";var foo = function foo(a, b) {return Object.assign(a, b);};

箭头 function 被编译成了普通函数,但丫的 Object.assign 还没变身,而它作为 ES6 的新方法,并不能在IE9等浏览器上。为什么不把 Object.assign 编译成 (Object.assign||function() { /*...*/}) 这样的替代方法呢?好问题!编译为了保证正确的语义,只转换语法而不是去增加或修改原有的属性和方法。所以 babel 不处理 Object.assign 反倒是最正确的做法。而处理这些方法的方案则称为 polyfill。

babel-plugin-transform-xxx

这个问题最原始解决思路是缺什么补什么,babel 提供了一系列 transform 的插件来解决这个问题,例如针对 Object.assign,我们可以使用 babel-plugin-transform-object-assign:

npm i babel-plugin-transform-object-assign# in .babelrc
{"presets": ["latest"],"plugins": ["transform-object-assign"]
}

方便你尝试,这里准备了一些测试的代码。编译之前的代码,我们得到了:

var _extends = Object.assign || function (target) { for (var i = 1; i source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };var foo = exports.foo = function foo(a, b) {return _extends(a, b);
};

babel-plugin-transform-object-assign 在我们用到 Object.assign 方法之前使用ES5或更早的写法替换了。看上去效果不错,但细细考究一下会发现这样的问题:

// another.js
export const bar = (a, b) => Object.assign(a, b);// index.js
import { bar } from './another';export const foo = (a, b) => Object.assign(a, b);

被编译成了:

/***/ index.js:
/***/ (function(module, exports, __webpack_require__) {"use strict";Object.defineProperty(exports, "__esModule", {value: true
});
exports.foo = undefined;var _extends = Object.assign || function (target) { for (var i = 1; i source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };var _another = __webpack_require__(212);var foo = exports.foo = function foo(a, b) {return _extends(a, b);
};/***/ }),/***/ another.js:
/***/ (function(module, exports, __webpack_require__) {"use strict";Object.defineProperty(exports, "__esModule", {value: true
});var _extends = Object.assign || function (target) { for (var i = 1; i source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };var bar = exports.bar = function bar(a, b) {return _extends(a, b);
};/***/ })

plugin-transform 的引用是 module 级别的,意味着在多个 module 使用时会重复的引用,这在多文件的项目里可能带来灾难。且也不想一个个的去添加需要用的 plugin,如果能自动引入该多好。

babel-runtime & babel-plugin-transform-runtime

前面提到问题主要在于方法的引入方式是内联的,直接插入了一行代码从而无法优化。鉴于这样的考虑,babel 提供了 babel-plugin-transform-runtime,从一个统一的地方 core-js 自动引入对应的方法。

npm i -D babel-plugin-transform-runtime
npm i babel-runtime# .babelrc
{"presets": ["latest"],"plugins": ["transform-runtime"]
}

  • 安装开发时的依赖 babel-plugin-transform-runtime。
  • 安装生产环境的依赖 babel-runtime (是否要在生产环境也依赖它取决于你发布代码的方式,简单点直接放在 dependency 里总没错)

一切就绪,编译时它会自动引入你用到的方法。但自动就意味着不一定精确

export const foo = (a, b) => Object.assign(a, b);export const bar = (a, b) => {const o = Object;const c = [1, 2, 3].includes(3);return c && o.assign(a, b);
};

会编译成:

var _assign = __webpack_require__(214);
var _assign2 = _interopRequireDefault(_assign);function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var foo = exports.foo = function foo(a, b) {return (0, _assign2.default)(a, b);
};var bar = exports.bar = function bar(a, b) {var o = Object;var c = [1, 2, 3].includes(3);return c && o.assign(a, b);
};

foo 中的 assign 会被替换成 require 来的方法,而 bar 中这样非直接调用的方式则无能为力了。同时,因为 babel-plugin-transform-runtime 依然不是全局生效的,因此实例化的对象方法则不能被 polyfill,比如 [1,2,3].includes 这样依赖于全局 Array.prototype.includes 的调用依然无法使用。

babel-polyfill

上面两种 polyfill 方案共有的缺陷在于作用域。因此 babel 直接提供了通过改变全局来兼容 es2015 所有方法的 babel-polyfill,安装 babel-polyfill 后你只需要在main.js加一句 import 'babel-polyfill' 便可引入它,如果使用了 webpack 也可以直接在 entry 中添加 babel-polyfill 的入口。

import 'babel-polyfill';export const foo = (a, b) => Object.assign(a, b);

加入 babel-polyfill 后,打包好的 pollyfill.js 一下子增加到了 251kb(未压缩),(建议感兴趣的同学把代码拉下来运行一下,之后提到的所有方式也都可以看到打包结果)搜索一下 polyfill.js 不难找到这样的全局修改:

//polyfill
`$export($export.S + $export.F, 'Object', {assign: __webpack_require__(79)});

babel-polyfill 在项目代码前插入所有的 polyfill 代码,为你的程序打造一个完美的 es2015 运行环境。babel 建议在网页应用程序里使用 babel-polyfill,只要不在意它略有点大的体积(min 后 86kb),直接用它肯定是最稳妥的。值得注意的是,因为 babel-polyfill 带来的改变是全局的,所以无需多次引用,也有可能因此产生冲突,所以最好还是把它抽成一个 common module,放在项目 的 vendor 里,或者干脆直接抽成一个文件放在 cdn 上。

如果你是在开发一个库或者框架,那么 babel-polyfill 的体积就有点大了,尤其是在你实际使用的只有一个 Object.assign 的情况下。更可怕的是对于一个库来说,改变全局环境是使不得的。谁也不希望使用了你的库,还附带了一家老小的 polyfill 改变了全局对象。这时不污染全局环境的 babel-plugin-transform-runtime 才是最合适的。

babel-preset-env

回到应用开发。通过babel-runtime自动识别代码引入 polyfill 来优化不太靠谱,那是不是就无从优化了呢?并不是。还记得 babel 推荐使用的 babel-preset-env 么?它可以根据指定目标环境判断需要做哪些编译。babel-preset-env 也支持针对指定目标环境选择需要的 polyfill 了,只需引入 babel-polyfill,并在 babelrc 中声明 useBuiltIns,babel 会将引入的 babel-polyfill 自动替换为所需的 polyfill。

  1. targets 指定需要兼容的浏览器类型和版本,
  2. 如果用 Node.js 开发,也同样可以指定 Node 的版本, 也可以直接写成 "node": "current",将自动采用你当前用来运行 Babel 的 Node.js 版本
  3. modules 用来指定模块化方式,支持 AMD、UMD、SystemJS、CommonJS 等。当然在 Webpack 2/3 的时代,推荐将 modules 设置为 false,即交由 Webpack 来处理模块化,通过其 TreeShaking 特性将有效减少打包出来的 JS 文件大小

# .babelrc
{"presets": [["env", {"modules": false,"targets": {"browsers": ["last 2 versions", "safari >= 7", "IE >= 9" ],"node": "current", // 自动采用你当前用来运行 Babel 的 Node.js 版本"modules": false },"useBuiltIns": "entry", // entry usage falseinclude: []}],"stage-2"],"plugins": ["transform-vue-jsx", "transform-runtime"],"env": {"test": {"presets": ["env", "stage-2"],"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]}}
}

对比 "IE >= 9" 和 "chrome >= 59" 环境下编译后的文件大小:

Asset Size Chunks polyfill.js 252 kB 0 [emitted] [big]ie9.js 189 kB 1 [emitted]chrome.js 30.5 kB 2 [emitted]
transform-runtime.js 17.3 kB 3 [emitted]
transform-plugins.js 3.48 kB 4 [emitted]

在目前 IE9 的需求下能节省到将近 30%,但想不到浏览器之神 chrome 也还需要 30kb 的 polyfill,可能是为了修正那些 v8 的一些细小的规范问题吧。

polyfill.io

以上本应该已经够用了,但本质上还是让那些愿意使用最新浏览器的优质用户们做了牺牲。聪明的你可能已经想到了一种优化方案,针对浏览器来选择 polyfill。没错!polyfill.io 给出的一项服务。

你可以尝试在不同的浏览器下请求 https://cdn.polyfill.io/v2/polyfill.js 这个文件,服务器会判断浏览器 UA 返回不同的 polyfill 文件,你所要做的仅仅是在页面上引入这个文件,polyfill 这件事就自动以最优雅的方式解决了。更加让人喜悦的是,polyfill.io 不旦提供了 cdn 的服务,也开源了自己的实现方案 polyfill-service。简单配置一下,便可拥有自己的 polyfill service 了。

看上去一切都很美好,但在使用之前还请你多考虑一下。polyfill.io 面对国内奇葩的浏览器环境能不能把 UA 算准,如果缺失了 polyfill 还有没有什么补救方案,也许都是你需要考虑的。但无论如何,这是个优秀的想法和方案,或许未来也会有更多的网站采用 polyfill.io 的思路的。比如 theguardian 和 redux 作者 Dan 在 create-react-app 上的提议(虽然没被接受哈~)。

转:https://juejin.im/post/5cd178056fb9a0322415affd



推荐阅读
  • 深入浅出 webpack 系列(二):实现 PostCSS 代码的编译与优化
    在前一篇文章中,我们探讨了如何通过基础配置使 Webpack 完成 ES6 代码的编译。本文将深入讲解如何利用 Webpack 实现 PostCSS 代码的编译与优化,包括配置相关插件和加载器,以提升开发效率和代码质量。我们将详细介绍每个步骤,并提供实用示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • Vue应用预渲染技术详解与实践 ... [详细]
  • Node.js 教程第五讲:深入解析 EventEmitter(事件监听与发射机制)
    本文将深入探讨 Node.js 中的 EventEmitter 模块,详细介绍其在事件监听与发射机制中的应用。内容涵盖事件驱动的基本概念、如何在 Node.js 中注册和触发自定义事件,以及 EventEmitter 的核心 API 和使用方法。通过本教程,读者将能够全面理解并熟练运用 EventEmitter 进行高效的事件处理。 ... [详细]
  • 单元测试:使用mocha和should.js搭建nodejs的单元测试
    2019独角兽企业重金招聘Python工程师标准BDD测试利器:mochashould.js众所周知对于任何一个项目来说,做好单元测试都是必不可少 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • 本文介绍了如何使用 Node.js 和 Express(4.x 及以上版本)构建高效的文件上传功能。通过引入 `multer` 中间件,可以轻松实现文件上传。首先,需要通过 `npm install multer` 安装该中间件。接着,在 Express 应用中配置 `multer`,以处理多部分表单数据。本文详细讲解了 `multer` 的基本用法和高级配置,帮助开发者快速搭建稳定可靠的文件上传服务。 ... [详细]
  • 【实例简介】本文详细介绍了如何在PHP中实现微信支付的退款功能,并提供了订单创建类的完整代码及调用示例。在配置过程中,需确保正确设置相关参数,特别是证书路径应根据项目实际情况进行调整。为了保证系统的安全性,存放证书的目录需要设置为可读权限。值得注意的是,普通支付操作无需证书,但在执行退款操作时必须提供证书。此外,本文还对常见的错误处理和调试技巧进行了说明,帮助开发者快速定位和解决问题。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 深入解析:React与Webpack配置进阶指南(第二部分)
    在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]
  • 解决lib-flexible安装过程中遇到的错误问题
    在安装 lib-flexible 时,遇到了 `saveError ENOENT: No such file or directory` 错误,具体表现为无法打开 `E:\Github\SDIO\package.json` 文件。解决此问题的关键在于确保项目根目录下存在 `package.json` 文件,并且在正确的项目路径中执行安装命令。建议先检查项目结构,确认文件是否存在,然后再尝试重新安装依赖。 ... [详细]
  • 本文详细介绍了在 Vue.js 前端框架中集成 vue-i18n 插件以实现多语言支持的方法。通过具体的配置步骤和示例代码,帮助开发者快速掌握如何在项目中实现国际化功能,提升用户体验。同时,文章还探讨了常见的多语言切换问题及解决方案,为开发人员提供了实用的参考。 ... [详细]
  • TypeScript 实战分享:Google 工程师深度解析 TypeScript 开发经验与心得
    TypeScript 实战分享:Google 工程师深度解析 TypeScript 开发经验与心得 ... [详细]
  • 深入解析 Vue 中的 Axios 请求库
    本文深入探讨了 Vue 中的 Axios 请求库,详细解析了其核心功能与使用方法。Axios 是一个基于 Promise 的 HTTP 客户端,支持浏览器和 Node.js 环境。文章首先介绍了 Axios 的基本概念,随后通过具体示例展示了如何在 Vue 项目中集成和使用 Axios 进行数据请求。无论你是初学者还是有经验的开发者,本文都能为你解决 Vue.js 相关问题提供有价值的参考。 ... [详细]
  • 本地存储组件实现对IE低版本浏览器的兼容性支持 ... [详细]
  • 如何在 Node.js 环境中将 CSV 数据转换为标准的 JSON 文件格式? ... [详细]
author-avatar
aizhezhe
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有