原文连接node
与我交流过的绝大多数web开发者,都喜欢使用全部新的语法特性(如async/await,类,箭头函数等)。尽管全部现代浏览器都支持以上的语法,多部分开发者仍然会转译到ES5而且加上polyfill以便支持哪一小部分仍旧使用老版本浏览器的用户。webpack
这...有点糟。在理想的的世界中,是没有没必要要的代码!git
新版本的JS和DOM接口能让咱们选择性地加载polyfill,由于在运行时,咱们能够检测浏览器对新特性的支持状况。可是新的JS语法有一点很差,由于没法识别的语法都会形成解析错误,致使没有代码会被执行。github
虽然如今并无对feature-detecting
这个语法的好的解决方案,但咱们确实有一个方法能作到ES2015语法支持的检测。web
这就是<script type="module">
npm
多数开发者将<script type="module">
视为加载ES模块的一种方式(这没毛病啊),但<script type="module">
也有更直接且实际的加载常规JavaScript文件与ES2015+功能,并知道浏览器能处理它!浏览器
换言之,全部支持<script type="module">
语法的浏览器也支持绝大多数你爱的那些ES2015+属性。例如:缓存
剩下的事就是为不支持<script type="module">
的浏览器提供一个降级的处理。若是你正在生成一个ES5版本的代码,那么恭喜你你已经作好这一步了,如今你须要作的就是生成一个ES2015+的版本!babel
本篇余下的部分将讨论如何实现这一技术以及发布ES2015+代码的能力将如何改变咱们编写模块的方式。async
若是你已经上手了webpack或rollup这样的工具来生成你的JS,那就继续吧!
下一步,在现有bundle的基础上,你要生成第二份bundle。与第一份bundle的惟一区别就是你再也不须要把代码转译到ES5版本,同时你也不用在引入任何polyfill。
在使用babel-preset-env的前提下,第二步是很是简单的。你惟一要作的就是改变配置中的浏览器列表到支持<script type="module">
的浏览器,这样Babel就不会进行那些没必要要的转译。
换言之,用ES2015+的代码取代ES5。
举个例子,若是你在使用webpack,entry的路径是./path/to/main.js
。目前的配置(要编译成ES5版本的)应该大体以下(我会把这个bundle曾为'古早版',由于它是ES6的):
module.exports = { entry: { 'main-legacy': './path/to/main.js', }, output: { filename: '[name].js', path: path.resolve(__dirname, 'public'), }, module: { rules: [{ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: [ ['env', { modules: false, useBuiltIns: true, targets: { browsers: [ '> 1%', 'last 2 versions', 'Firefox ESR', ], }, }], ], }, }, }], }, };
要获得一个现代的支持ES2015+的版本。你要作的仅仅是将目标环境改为支持<script type="module">
的浏览器,像下面这样:
module.exports = { entry: { 'main': './path/to/main.js', }, output: { filename: '[name].js', path: path.resolve(__dirname, 'public'), }, module: { rules: [{ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: [ ['env', { modules: false, useBuiltIns: true, targets: { browsers: [ 'Chrome >= 60', 'Safari >= 10.1', 'iOS >= 10.3', 'Firefox >= 54', 'Edge >= 15', ], }, }], ], }, }, }], }, };
当你执行构建时,这两个配置文件会获得两个JS文件:
下一步,就是更新你的HTML来支持选择性加载JS bundle。你同时可使用<script type="module">
和<script nomodule>
来实现。
<!-- 支持ES模块的浏览器去下载下面这个文件 --> <script type="module" src="main.js"></script> <!-- 老版本浏览器则加载下面这个文件 --> <script nomodule src="main-legacy.js"></script>
小贴士:可恶的Safari 10并不支持nomodule
属性,不过你能够在HTML前部引入 safari-nomodule.js来解决这一问题。(好在,在Safari 11种他们解决了这个问题,我到都拔出来了)
!
这多数状况下,这个方法“能用”。可是在使用这一策略前,咱们须要了解模块加载的一些细节。
<script defer>
语言同样被加载,这就意味着。知道文件被解析前都不会被执行。若是你有一些代码须要先行,请把它们拆分出来,而后单独引用。严格模式
,因此若是出于某种缘由,你不要使用严格模式,请拆分出这部分代码,并单独引用。var foo = 'bar'
或是 函数声明function foo() {…}
的变量能够经过window.foo
访问。但在一个模块中却并不是如此。因此这可能会成为你书写代码时的一个坑!我建立了一个模版项目,读者能够看到这一方法在实际工做中的应用。
在这个模版中,我试用了许多新出的webpack特性,由于这个技术在实际工做中真的能用。摆脱,我可不是赵括。这些特性包括咱们常见的实践:
我不会用本身不会的技术,若是你想要了解更多欢迎阅读源代码
若是你并不是使用webpack来生成生产环境的bundle,过程也大同小异。我之因此选择webpack,由于它是当下最流行的,但它也是最复杂的!若是webpack能用,那么其余工具也能使用。
在我看来必须的,这些付出是值得的。下表比较了两种版本最终生成文件的实际大小:
即使通过Gzip传统ES5版本也是ES2015+版本体积的两倍。
大致积文件不尽更耗费时间去加载,同时,也须要更长时间解析与执行。这两个版本的解析/执行时间依旧是两倍的关系。(这个测试我试用了webpagetest.org提供的 Moto G4)
虽然这些独立的文件不大,解析/执行的时间也不是特别长,但这仅仅是个博客。若是是外头那些庞然大物,ES2015+你绝对值得拥有!
一项来之HTTPArchive数据的统计显示。Alexa排名前列的网站中有85181在他们的项目中使用了babel-polyfill
, core-js
, 或是regenerator-runtime
。6个月前这个数字是34588!
现实就是转译以及使用polyfill正迅速成为新的标准。不幸的事,大部分用户正所以牺牲了流量来下载这些原本能够更小的文件。
如今的问题就是开发者并无发布ES2015+版本的代码,而是发布了转译后的ES5版本。
但如今ES2015+是能够部署的,因此是时候去改变了。
我彻底明白,这会带来一些阵痛。 现在大多数构建工具发布的文档,都推荐ES5的配置。 这意味着,若是模块做者开始向npm发布ES2015 +源代码,他们可能会破坏一些用户的构建,这将会形成混乱。
问题是大多数使用Babel的开发人员将它配置为不在node_modules
中传输任何内容,可是若是模块是使用ES2015 +源代码发布的,则这是一个问题。 幸运的是修复很简单。 您只需从构建配置中删除node_modules排除:
rules: [ { test: /\.js$/, exclude: /node_modules/, // 移除这行 use: { loader: 'babel-loader', options: { presets: ['env'] } } } ]
弊端就是,babel
这样的工具不只仅须要本地依赖关系,在执行时还须要在node_modules
中传递这种关系。这样构件会变慢。不过这一问题能够在持续化的本地缓存工具上获得解决。
纵使前途坎坷,咱们也因该为提高用户体验大步向前。经过发布ES2015,咱们为开发人员提供了一种选择,并最终惠及每一个人。
<script type="module">
的价值远不只仅是为了在浏览器中加载ES模块。
在支持这一特性的现代浏览器中,<script type="module">
能够给予开发者,选择性加载单一JS文件的预定体验。
这与nomodule属性一块儿,为咱们提供了一种在生产环境中使用ES2015+代码的方法,咱们终于能够中止向不须要它的用户发送如此多的代码。
编写ES2015代码对开发者来讲是一个胜利,部署ES2015代码对用户来讲是一个胜利。