TL;DR Use the FeatureTests.io service to perform feature tests of ES6+ features. The results of these tests are cached by default in the user's browser, and shared across all sites the user visits that use this service.
TL; DR使用FeatureTests.io服务执行ES6 +功能的功能测试。 这些测试的结果默认情况下缓存在用户的浏览器中,并在用户使用该服务访问的所有站点之间共享。
In the bootstrapper for your site/app, check the results of these feature tests to decide which files are appropriate to load.
在您的网站/应用程序的引导程序中,检查这些功能测试的结果,以确定适合加载的文件。
If the tests pass, you can load your original source *.es6.js
files and know they'll work natively and performantly just fine in that browser. If any test fails, fall back to loading the already build-step pre-transpiled *.es5.js
versions of your code.
如果测试通过,则可以加载原始的*.es6.js
源文件,并知道它们将在该浏览器中以本机方式正常运行。 如果任何测试失败,则退回到加载已经构建步骤的预编译的*.es5.js
版本的代码。
Use the same checking logic to decide if the user's browser needs a big shim library (like ES6-Shim) or if the browser needs none (or only a few) of the API polyfills.
使用相同的检查逻辑来确定用户的浏览器是否需要大的填充库(例如ES6-Shim ),或者浏览器是否不需要(或仅需要几个)API polyfill。
Essentially: load only the code that's necessary, and load the best, most native version of it that the browser can support.
本质上: 仅加载必要的代码 ,并加载浏览器可以支持的最佳,最原始的版本 。
问题 (The Problem)
If you're using any ES6+ code in your applications, odds are you're using a transpiler like Babel or perhaps Traceur. These tools are fantastic and quite capable of producing transpiled versions of your ES6+ code that can run in ES5+ browsers (the vast majority).
如果您在应用程序中使用任何ES6 +代码,则很可能使用的是Babel或Traceur之类的编译器 。 这些工具非常棒,并且能够生成可在ES5 +浏览器(绝大多数)中运行的ES6 +代码的转译版本。
However, there's a nuance that is being largely overlooked, and the point of this post is to bring it to light as motivation for a new service I've launched to help address the concern: FeatureTests.io.
但是,有一个细微差别在很大程度上被忽略了,这篇文章的重点是揭示它是我为帮助解决这一问题而推出的一项新服务的动机: FeatureTests.io 。
Let me pose this rhetorical question/scenario to perhaps illustrate my concern:
让我提出这个修辞问题/场景以说明我的关注:
Let's assume TC39 keeps adding new and amazing capabilities to the language specification. But why do the browsers need to implement any of these features? Couldn't we just always rely on transpilers, forever going forward, and couldn't we just always and only serve those transpiled files to the browser? If so, wouldn't that mean these features would never actually need to make their way into a browser? The ES specification could just become a transpiler specification, right?
假设TC39一直在语言规范中添加新的惊人功能。 但是,为什么浏览器需要实现所有这些功能? 我们难道不能永远依赖编译器,永远向前,难道我们不能总是仅将那些已编译的文件提供给浏览器吗? 如果是这样,这是否意味着这些功能实际上根本不需要进入浏览器? ES规范可能只是成为编译器规范,对吗?
...
...
If you ponder that scenario for just a moment or two, odds are several concerns jump out at you. Most notably, you probably realize the transpiled code that's produced is bigger, and perhaps slower (if not now, certainly later once browsers have a chance to optimize the native feature implementations). It also requires shipping dozens of kb of polyfill code to patch the API space in the browser.
如果您只想想一两个场景,很可能会遇到几个问题。 最值得一提的是,您可能意识到所生成的转译代码更大,并且可能更慢(如果不是现在,那么肯定是在浏览器有机会优化本机功能实现之后的更晚版本)。 它还需要传送数十kb的polyfill代码以修补浏览器中的API空间。
This all works, but it's not ideal. The best code you can deliver to each user's browser is the smallest, fastest, most well-tailored code you can practically provide. Right!?
所有这些都可以, 但是并不理想 。 您可以提供给每个用户浏览器的最佳代码是您可以实际提供的最小,最快,量身定制的代码。 对!?
Here's the problem: if you only use a build-step transpiler and you unconditionally always serve that ES5 equivalent transpiled code, you will never actually be using any of the native feature implementations. You'll always and forever be using the older, bigger, (perhaps) slower transpiled code.
问题就出在这里:如果您仅使用构建步骤的编译器,并且无条件地始终提供与ES5等效的编译代码,则您将永远不会真正使用任何本机功能实现。 您将永远永远使用旧的,较大的(也许)较慢的转译代码。
For now, while ES6 browser support seems to linger in the lower percentages, that may not seem like such a huge deal. Except, have you actually considered just how much of ES6 your app/site is using (or will use soon)?
目前,虽然对ES6浏览器的支持似乎停留在较低的百分比上,但这似乎并不算什么。 除了,您实际上是否考虑过您的应用程序/站点正在使用(或即将使用)多少ES6?
My guess is, most sites will use maybe 20-30% of ES6 features on a widespread basis. And most if not all of those are already implemented in just about every browser's latest version. Moreover, the new Microsoft Edge browser already has 81% ES6 support (at the time of this writing), and FF/Chrome at ~50-60% are going to quickly catch up.
我的猜测是,大多数站点可能会广泛使用ES6功能的20-30%。 几乎所有浏览器的最新版本都已经实现了大多数(如果不是全部的话)。 而且,新的Microsoft Edge浏览器已经拥有81%的ES6支持 (在撰写本文时),而FF / Chrome的〜50-60%将Swift赶上。
It won't be long at all before a significant chunk of your users have full ES6 support for every feature your site/app uses or will practically use in the near future.
不久之后,您的很大一部分用户就将对您的站点/应用程序使用的所有功能或即将在不久的将来实际使用的所有功能提供全面的ES6支持。
Don't you want to serve each user the best possible code?
您是否不想为每个用户提供最好的代码?
解决方案 (The Solution)
First and foremost, keep transpiling your code using your favorite tool(s). Keep doing this in a build-step.
首先,请继续使用您喜欢的工具来转译代码。 继续按照构建步骤进行操作。
When you go to deploy the .js
files to your web-exposed directory that can be loaded into the browser, include the original (ES6+) source files as well as these transpiled files. Also, don't forget to include the polyfills as necessary. For instance, you may name them *.es6.js
(original source) and *.es5.js
(transpiled) to keep them straight. Or, you may use subdirectories es6/
and es5/
to organize them. You get the point, I'm sure.
当您将.js
文件部署到可加载到浏览器的Web公开目录中时,请包括原始(ES6 +)源文件以及这些已转换的文件。 另外,请不要忘记在必要时包含polyfills。 例如,您可以将它们命名为*.es6.js
(原始源代码)和*.es5.js
(已编译),以使其保持直线。 或者,您可以使用子目录es6/
和es5/
来组织它们。 我确定,你明白了。
Now, how do you decide when your site/app goes to load the first time which set of files is appropriate to load for each users' browser?
现在,您如何确定您的网站/应用首次加载的时间是适合每个用户浏览器加载的文件集?
You need a bootstrapper that loads first, right up front. For instance, you ship out an HTML page with a single <script>
tag in it, and it either includes inline code, or a reference to a single .js
file. Many sites/apps of any complexity already do this in some form or another. It's quite typical to load a small bootstrapper that then sets up and loads the rest of your application.
您需要一个引导程序,该引导程序会首先加载,然后就在前面。 例如,您出厂时带有单个<script>
标记HTML页面,该页面要么包含内联代码,要么包含对单个.js
文件的引用。 许多具有各种复杂性的网站/应用程序都已经以某种形式进行了此操作。 加载一个小的引导程序,然后设置并加载应用程序的其余部分,这是非常典型的。
If you don't already have a technique like this, it's not hard to do at all, and there are many benefits you'll get, including the ability to conditionally load the appropriate versions of files for each browser, as I will explain in a moment. Really, this is not as intimidating as it may seem.
如果您还没有这样的技术,那根本就不难做,您将获得许多好处,包括有条件地为每个浏览器加载适当版本的文件的能力,正如我将在下面解释的那样。片刻。 确实,这并不像看起来那样令人生畏。
Now, in your bootstrapper (however your's is set up), how are you going to decide what files to load?
现在,在引导程序中(无论如何设置),如何决定要加载哪些文件?
You need to feature test that browser instance to decide what its capabilities are. If all the features you need are supported, load the *.es6.js
files. If some are missing, load the polyfills and the *.es5.js
files.
您需要对浏览器实例进行功能测试 ,以决定其功能。 如果支持所需的所有功能,请加载*.es6.js
文件。 如果缺少某些内容,请加载*.es5.js
和*.es5.js
文件。
That's it. Really. No, really, that's all I'm suggesting.
而已。 真。 不,真的,这就是我的建议。
功能测试ES6 (Feature Testing ES6)
Feature testing for APIs is easy. I'm sure you probably know how to do things like:
API的功能测试很容易。 我确定您可能知道如何做这样的事情:
if (Number.isNaN) {
numberIsNaN = true;
}
else {
numberIsNaN = false;
}
But what about syntax, like detecting if the browser supports =>
arrow functions or the let
block-scoping declarations?
但是语法如何处理,例如检测浏览器是否支持=>
箭头功能或let
块作用域声明?
That's harder, because this doesn't work the way we might hope:
这很难,因为这无法像我们希望的那样工作:
try {
x = y => y;
arrows = true;
}
catch (err) {
arrows = false;
}
The syntax fails JS compilation (in pre-ES6 compliant browsers) before it ever tries to run, so the try..catch
can't catch it. The solution? Defer compilation.
该语法在尝试运行之前会使JS编译(在符合ES6之前的版本的浏览器中)失败,因此try..catch
无法捕获它。 解决方案? 推迟编译。
try {
new Function( "(y => y)" );
arrows = true;
}
catch (err) {
arrows = false;
}
The new Function(..)
constructor compiles the code given at runtime, so any compilation error can be caught by your try..catch
.
new Function(..)
构造函数将编译运行时给出的代码,因此try..catch
可以捕获任何编译错误。
Great, problem solved.
太好了,问题解决了。
But do you want to personally devise feature tests for all the different ES6+ features you plan to use? And some of them could be slightly painful (slow) to run (like for TCO), so do you really want to do those? Wouldn't it be nicer to run the tests in a background Web Worker thread to minimize any performance impact to the main UI thread?
但是,您是否想针对计划使用的所有不同ES6 +功能亲自设计功能测试? 而且其中一些可能会有点痛苦(运行缓慢)(例如TCO),那么您真的要这样做吗? 在后台Web Worker线程中运行测试以最大程度地减少对主UI线程的性能影响会更好吗?
And even if you did go to all that trouble, do you really need to run all these tests every single time one of your pages loads? Browsers don't add new features by the minute. Typically, a user's browser might update at best every couple of weeks, maybe months. Couldn't you run the tests once and cache the results for awhile?
即使您确实遇到了所有麻烦,您是否真的需要在每次加载一个页面时都运行所有这些测试? 浏览器不会立即添加新功能。 通常,用户的浏览器最多每两周(可能是几个月)更新一次。 您不能运行测试一次并缓存结果一段时间吗?
But if these cached results are only available to your site, if your user visits other ES6-driven sites, every one of them will need to re-perform their own set of the tests. Wouldn't it be nicer if the test results could be cached "globally" on that user's browser, so that any site could just use the true
/ false
test results without having to re-run all the tests?
但是,如果这些缓存的结果仅对您的站点可用,并且您的用户访问了其他ES6驱动的站点,则每个站点都将需要重新执行自己的一组测试。 那岂不是更好,如果测试结果可以被缓存“全局”对用户的浏览器,让任何网站可以只使用true
/ false
的测试结果,而无需重新运行所有测试?
Or let me turn that around: wouldn't it be nice if your user showed up at your site and the results were already cached (by a visit to another site), so they didn't need to wait for your site to run them, and thus your site loaded quicker for them?
或者让我扭转一下:如果您的用户出现在您的网站上并且结果已经被缓存(通过访问另一个网站),那不是很好,因此他们不需要等待您的网站运行它们,因此您的网站为他们加载的更快?
FeatureTests.io (FeatureTests.io)
All these reasons (and more) are why I've built ES Feature Tests as a service: FeatureTests.io.
所有这些原因(以及更多)是为什么我将ES Feature Tests作为服务构建的原因: FeatureTests.io 。
This service provides a library file https://featuretests.io/rs.js which does all the work I referred to above for you. You request this library file either before or as your bootstrapper loads, and then you simply check the results of the tests (which load from cache or run automatically) with a simple if
statement.
该服务提供了一个库文件https://featuretests.io/rs.js ,该文件可以完成我在上面为您提到的所有工作。 您之前或作为你的引导程序加载要求该库文件,然后你只需用一个简单的检查测试的结果(从缓存中哪些负载或自动运行) if
语句。
For example, to test if your let
and =>
using files can load, this is what you'd do in your bootstrapper:
例如,要测试您的let
和=>
using文件是否可以加载,可以在引导程序中执行以下操作:
window["Reflect.supports"]( "all", function(results){
if (results.letConst && results.arrow) {
// load `*.es6.js` files
}
else {
// load already pre-transpiled `*.es5.js` files
}
} );
If your site hasn't already cached results for this user, the library cross-domain communicates (via <iframe>
from your site to featuretests.io
) so the test results can be stored or retrieved "globally" on that browser.
如果您的站点尚未为该用户缓存结果,则库跨域通信(通过<iframe>
从您的站点传递到featuretests.io
),以便可以在该浏览器上“全局”存储或检索测试结果。
If the tests need to run, it spins up a Web Worker to do the tests off-thread. It even tries to use a Shared Web Worker, so that if the user is simultaneously loading 2+ sites that both use the service, they both use the same worker instance.
如果需要运行测试,它将启动Web Worker进行线程外测试。 它甚至尝试使用共享Web Worker,这样,如果用户同时加载两个以上都使用该服务的站点,则它们都将使用相同的worker实例。
All that logic you get automatically by using this free service.
使用此免费服务,您会自动获得所有这些逻辑。
That's it! That's all it takes to get up and going with conditional split-loading of your site/app code based on in-browser ES6 feature tests.
而已! 这就是根据浏览器内ES6功能测试有条件地分拆站点/应用程序代码所需的全部工作。
高级的东西 (Advanced Stuff)
The library behind this site is open-sourced: es-feature-tests. It's also available on npm.
该站点背后的库是开源的: es-feature-tests 。 也可以在npm上使用 。
If you wanted to, you could inline the tests from the library into your own bootstrapper code, and skip using FeatureTests.io. That loses you the benefits of shared caching and all, but it still means you don't have to figure out your own tests.
如果愿意,可以将库中的测试内联到您自己的引导程序代码中,然后跳过使用FeatureTests.io。 这使您失去了共享缓存和所有缓存的好处,但这仍然意味着您不必弄清楚自己的测试。
Or, the service offers an API endpoint that returns the tests in text form, so you could retrieve that on your server during your build step, and then include and perform those tests in your own code.
或者,该服务提供了一个API端点 ,该端点以文本形式返回测试,因此您可以在构建步骤中在服务器上检索该测试,然后在自己的代码中包含并执行这些测试。
The npm package is of course Node/iojs compatible, so you can even run the exact same sort of feature testing for split loading inside of your Node programs, like:
npm软件包当然是与Node / iojs兼容的,因此您甚至可以对Node程序内部的拆分加载运行完全相同的功能测试,例如:
var ReflectSupports = require("es-feature-tests");
ReflectSupports( "all", function(results){
if (results.letConst && results.arrow) {
// require(..) `*.es6.js` modules
}
else {
// require(..) already pre-transpiled
// `*.es5.js` modules
}
} );
我的代码需要哪些测试结果? (Which test results does my code need?)
As I asserted earlier, you likely won't need to check every single test result, as you likely won't use 100% of all ES6+ features.
正如我之前所说,您可能不需要检查每个测试结果,因为您可能不会使用所有ES6 +功能的100%。
But constantly keeping track of which test results your if
statement should check can be tedious and error-prone. Do you remember if anyone ever used a let
in your code or not?
但是,不断跟踪if
语句应检查哪些测试结果可能是乏味且容易出错的。 您还记得是否有人在您的代码中使用过let
?
The "es-feature-tests" package includes a CLI tool called testify
which can scan files or directories of your ES6 authored code, and automatically produces the equivalent check logic for you. For example:
“ es-feature-tests”软件包包括一个名为testify
的CLI工具,它可以扫描ES6编写代码的文件或目录,并自动为您生成等效的检查逻辑。 例如:
$> bin/testify --dir=/path/to/es6-code/
function checkFeatureTests(testResults){return testResults.letConst&&testResults.arrow}
Warning: At the time of this writing, this testify
tool is extremely hackish and WiP. It will eventually do full and complete parsing, but for now it's really rough. Stay tuned to more updates on this tool soon!
警告:在撰写本文时,该testify
工具非常骇人,而且WiP。 最终它将完成完整和完整的解析,但是现在真的很粗糙。 请继续关注此工具的更多更新!
You can use testify
in your build-process (before transpilation, probably) to scan your ES6 source files and produce that checkFeatureTests(..)
function declaration that checks all test results your code needs.
您可以使用testify
在构建过程(transpilation之前,可能)来扫描你的ES6源文件和产生checkFeatureTests(..)
函数声明,检查所有的测试结果你的代码的需求。
Now, you inline include that code in with your bootstrapper, so it now reads:
现在,您在引导程序中内联包含该代码,因此它显示为:
// ..
function checkFeatureTests(testResults){return testResults.letConst&&testResults.arrow}
window["Reflect.supports"]( "all", function(results){
if (checkFeatureTests(results)) {
// load `*.es6.js` files
}
else {
// load already pre-transpiled `*.es5.js` files
}
} );
// ..
This build-step CLI tool will make it so your tests are always tuned to the code you've written, automatically, which lets you set it and forget it in terms of making sure your site/app code is always loaded in the best version possible for each browser.
使用此构建步骤的CLI工具,您可以始终自动将测试调整到您编写的代码,从而可以确保始终以最佳版本加载网站/应用程序代码,从而对其进行设置和忘记每个浏览器都可以。
摘要 (Summary)
I want you to write ES6 code, and I want you to start doing so today. I've written a book on ES6 to help you learn it: You Don't Know JS: ES6 & Beyond, which you can either read for free online, or purchase from O'Reilly or other book stores.
我希望您编写ES6代码,并且今天开始。 我已经写了一本关于ES6的书来帮助您学习它: 您不知道JS:ES6&Beyond ,您可以免费在线阅读,也可以从O'Reilly或其他书店购买。
But, I want you to be responsible and optimal with how you ship your ES6 code or the transpiled code to your user's browsers. I want us all to benefit from the amazing work that the browsers are doing on implementing these features natively.
但是,我希望您对将ES6代码或转码后的代码运送到用户浏览器的方式负责并保持最佳状态。 我希望我们所有人都能从浏览器在本地实现这些功能方面所做的出色工作中受益。
Load the best code for every browser -- no more, no less. Hopefully FeatureTests.io helps you with that goal.
为每个浏览器加载最佳的代码 -多多少少。 希望FeatureTests.io可以帮助您实现该目标。
Happy ES6'ing!
ES6'ing快乐!