大多数 Web 开发人员都喜欢编写具有所有最新语言特性的 JavaScript——async/await、类、箭头函数等。然而,尽管事实上所有现代浏览器都可以运行 ES2015+ 代码并原生支持我刚才提到的特性 , 大多数开发人员仍然将他们的代码转换为 ES5 并将其与 polyfills 捆绑在一起,以适应仍在使用旧版浏览器的一小部分用户。

这有点糟糕。 在理想情况下,我们不会写不必要的代码。

使用新的 JavaScript 和 DOM API,我们可以有条件地加载 polyfill,因为我们可以在运行时检测它们的支持。 但是对于新的 JavaScript 语法,这要复杂得多,因为任何未知的语法都会导致解析错误,然后所有代码都不会运行。

虽然我们目前没有一个好的解决方案来检测新语法的特性,但我们现在有办法检测基本的 ES2015 语法支持。

解决方案是 <script type="module">

大多数开发人员认为 <script type="module"> 是加载 ES 模块的方式(当然这是真的),但是 <script type="module"> 还有一个更直接和实用的用例——加载常规 JavaScript 具有 ES2015+ 特性并且知道浏览器可以处理的文件!

换句话说,每个支持 <script type="module"> 的浏览器也支持你所知道和喜爱的大部分 ES2015+ 特性。 例如:

  • 每个支持 <script type="module"> 的浏览器也支持 async/await
  • 每个支持 <script type="module"> 的浏览器也支持类。
  • 每个支持 <script type="module"> 的浏览器也支持箭头功能。
  • 每个支持 <script type="module"> 的浏览器也支持 fetch、Promises、Map、Set 等等!

剩下要做的唯一一件事就是为不支持 <script type="module"> 的浏览器提供回退。 幸运的是,如果我们当前正在生成代码的 ES5 版本,那么我们已经完成了这项工作。 我们现在只需要生成一个 ES2015+ 版本!

本文的其余部分解释了如何实现此技术,并讨论了发布 ES2015+ 代码的能力将如何改变我们编写模块的方式。


实现

如果你现在已经在使用像 webpackrollup 这样的模块打包器来生成你的 JavaScript,你应该继续这样做。

接下来,除了我们当前的捆绑包之外,我们将生成第二个捆绑包,就像第一个捆绑包一样; 唯一的区别是你不会一直转译到 ES5,也不需要包含遗留的 polyfill。

如果我们已经在使用 babel-preset-env(应该使用),则第二步非常简单。 你所要做的就是将你的浏览器列表更改为仅支持 <script type="module"> 的浏览器,Babel 将自动不应用它不需要的转换。

换句话说,它将输出 ES2015+ 代码而不是 ES5。

例如,如果我们正在使用 webpack 并且我们的主脚本入口点是 ./path/to/main.mjs,那么我们当前的 ES5 版本的配置可能看起来像这样(注意,我将这个包称为 main.mjs)。 es5.js 因为它是 ES5):

module.exports = {
entry: './path/to/main.mjs',
output: {
filename: 'main.es5.js',
path: path.resolve(__dirname, 'public'),
},
module: {
rules: [{
test: /\.m?js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['env', {
modules: false,
useBuiltIns: true,
targets: {
browsers: [
'> 1%',
'last 2 versions',
'Firefox ESR',
],
},
}],
],
},
},
}],
},
};

要制作一个现代的 ES2015+ 版本,我们所要做的就是进行第二个配置并将您的目标环境设置为仅包括支持 <script type="module"> 的浏览器。 它可能看起来像这样(注意,我在这里使用 .mjs 扩展名,因为它是一个模块):

module.exports = {
entry: './path/to/main.mjs',
output: {
filename: 'main.mjs',
path: path.resolve(__dirname, 'public'),
},
module: {
rules: [{
test: /\.m?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',
],
},
}],
],
},
},
}],
},
};

运行时,这两个配置将输出两个可用于生产的 JavaScript 文件:

  • main.mjs(语法为 ES2015+)
  • main.es5.js(语法为 ES5)

下一步是更新我们的 HTML 以在支持模块的浏览器中有条件地加载 ES2015+ 包。 我们可以使用 <script type="module"><script nomodule> 的组合来做到这一点:

<!-- Browsers with ES module support load this file. -->
<script type="module" src="main.mjs"></script> <!-- Older browsers load this file (and module-supporting -->
<!-- browsers know *not* to load this file). -->
<script nomodule src="main.es5.js"></script>

注意 :我们已经更新了本文中的示例,以对我作为模块加载的任何文件使用 .mjs 文件扩展名。 由于这种做法相对较新,如果我不指出在使用它时可能遇到的一些问题,那我们就是失职了:

  • 我们的 Web 服务器需要配置为使用 Content-Type 标头 text/javascript 提供 .mjs 文件。 如果我们的浏览器无法加载 .mjs 文件,这可能就是原因。
  • 如果我们使用 Webpack 和 babel-loader 来捆绑 JavaScript,我们可能已经复制/粘贴了一些仅转译 .js 文件的配置代码。 将配置中的正则表达式从 /\.js$/ 更改为 /\.m?js$/ 应该可以解决我们的问题。
  • 较旧的 webpack 版本不会为 .mjs 文件创建 sourcemap,但自 webpack 4.19.1 以来,此问题已得到修复。

重要注意事项

在大多数情况下,这种技术“有效”,但在实施这种策略之前,有一些关于如何加载模块的细节很重要,需要注意:

  1. 模块像 <script defer> 一样加载,这意味着它们在文档被解析之前不会被执行。 如果我们的某些代码需要在此之前运行,最好将该代码拆分出来并单独加载。
  2. 模块总是在严格模式下运行代码,因此如果出于任何原因您的任何代码需要在严格模式之外运行,则必须单独加载它。
  3. 模块对待顶级 var 和函数声明的方式与脚本不同。 例如,在脚本中 var foo = 'bar'function foo() {…} 可以从 window.foo 访问,但在模块中情况并非如此。 确保我们不依赖代码中的这种行为。

警告 ! Safari 10 不支持 nomodule 属性,但我们可以通过在使用任何 <script nomodule> 标记之前在 HTML 中内联一段 JavaScript 片段来解决这个问题。 (注意:这已在 Safari 11 中修复)。


是时候开始将我们的模块发布为 ES2015 了

目前该技术的主要问题是大多数模块作者不发布其源代码的 ES2015+ 版本,他们发布转译后的 ES5 版本。

现在可以部署 ES2015+ 代码了,是时候改变它了。

我完全理解这对不久的将来提出了许多挑战。 今天大多数构建工具都会发布文档,推荐假定所有模块都是 ES5 的配置。 这意味着如果模块作者开始将 ES2015+ 源代码发布到 npm,他们可能会破坏一些用户的构建并且通常会引起混淆。

问题是大多数使用 Babel 的开发人员将其配置为不在 node_modules 中转换任何内容,但是如果模块是使用 ES2015+ 源代码发布的,这就是一个问题。 幸运的是,修复很容易。 我们只需从构建配置中删除 node_modules 排除项:

rules: [
{
test: /\.m?js$/,
exclude: /node_modules/, // Remove this line
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}
]

不利的一面是,如果像 Babel 这样的工具除了本地依赖项之外还必须开始转译 node_modules 中的依赖项,那么构建速度会变慢。 幸运的是,这个问题可以在一定程度上通过持久的本地缓存在工具级别得到解决。

不管我们在 ES2015+ 成为新的模块发布标准的道路上可能会遇到什么坎坷,我认为这是一场值得一战的斗争。 如果我们作为模块作者,只将我们代码的 ES5 版本发布到 npm,我们就会将臃肿和缓慢的代码强加给我们的用户。

通过发布 ES2015,我们给了开发者一个选择,最终让每个人都受益。

在生产中部署 ES2015+ 代码的更多相关文章

  1. 在生产中部署ML前需要了解的事

    在生产中部署ML前需要了解的事 译自:What You Should Know before Deploying ML in Production MLOps的必要性 MLOps之所以重要,有几个原因 ...

  2. 往服务器部署thinkphp5代码时要注意 pathinfo的问题

    往服务器部署thinkphp5代码时要注意 pathinfo的问题 如果nginx没有做任何设置 要使用?s=/的方式访问地址 只需要修改3个地方就可以了,亲测成功,看代码有注解 location ~ ...

  3. 服务器用 git 进行部署出现代码冲突的处理

    服务器用 git 进行部署出现代码冲突的处理 起因: 由于项目是之前很久之前上传的,且并没上线.使用 git pull 进行代码更新时出现很多冲突. 因为服务器上的代码有移动过位置,不知道为什么就冲突 ...

  4. Nginx部署前端代码实现前后端分离

    实现前后端分离,可以让前后端独立开发.独立部署.独立单测,双方通过JSON进行数据交互. 对于前端开发人员来说,不用每次调试都需要启动或配置Java/Tomcat运行环境:对于后端开发人员来说 ,也不 ...

  5. Jenkins之自动部署、代码安全扫描、自动化接口测试

    搭建Jenkins wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.reporpm --i ...

  6. 通过nginx部署前端代码实现前后端分离

    实现前后端分离,可以让前后端独立开发.独立部署.独立单测,双方通过JSON进行数据交互. 对于前端开发人员来说,不用每次调试都需要启动或配置Java/Tomcat运行环境:对于后端开发人员来说 ,也不 ...

  7. ActiveMQ部署和代码尝试(二)

    部署和代码尝试 1. 部署在linux 上的acvtiveMQ 要可以通过前台windows 的页面访问,必须把linux 的IP和 windows的 IP 地址配置到同一个网关下 .这种情况一般都是 ...

  8. 解读与部署(三):基于 Kubernetes 的微服务部署即代码

    在基于 Kubernetes 的基础设施即代码一文中,我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊使用的基础设施是如何使用代码描述的,以及它的 ...

  9. J2EE(java)后台调用ArcGIS Engine(AE)的部署和代码

    arcgis的BS开发解决方案一直是个坑,主推的地图服务查询速度慢,需要异步,功能少.相对来说主要用于CS的AE功能更强大全面,只是部署有点复杂 本文软件环境: win7 sp1 64位 MyEcli ...

  10. 在Windows Server 2008上部署SVN代码管理总结

    这段时间在公司开发Flex程序,所以使用TortoiseSVN作为团队代码管理器,今天在公司服务器上部署SVN服务器,并实验成功,总结如下: 服务器环境: 操作系统:Windows Server 20 ...

随机推荐

  1. mac通过docker一键部署MySQL8

    目录 mac通过docker一键部署MySQL8 一.前言 二.系统配置 三.安装步骤 Dockerhub查看镜像地址 1.一键安装 1.1.克隆脚本 1.2.安装程序 1.2.1.安装程序详情 1. ...

  2. 齐博x1 万能fun 调用任意数据表 任意字段就是这么任性调用

    列举了几个常用的查询进行简单封装,虽然系统也有内置的但是很多人不大会就二次封装简化了一下. 这里只封装了一个条件 多个条件的自己再封装或者用标签解决比较好 这里只是说fun可以万能调用 1获取任意表的 ...

  3. 【第1篇】人工智能(AI)语音测试原理和实践---宣传

    ​前言 本文主要介绍作者关于人工智能(AI)语音测试的各方面知识点和实战技术. 本书共分为9章,第1.2章详细介绍人工智能(AI)语音测试各种知识点和人工智能(AI)语音交互原理:第3.4章介绍人工智 ...

  4. ES6 学习笔记(十)Map的基本用法

    1 基本用法 Map类型是键值对的有序列表,而键和值都可以是任意类型.可以看做Python中的字典(Dictionary)类型. 1.1 创建方法 Map本身是一个构造函数,用来生成Map实例,如: ...

  5. 重新整理 .net core 实践篇 ———— linux上性能排查 [外篇]

    前言 该文的前置篇为: https://www.cnblogs.com/aoximin/p/16839830.html 本文介绍性能排查. 正文 上一节是出现错误了,如何去排查具体问题. 这一节介绍一 ...

  6. EXCEL_BASIC

    公式类 比较大小 A1单元格的值大于B1单元格时为"A",小于时为"a",等于时为"e" =IF(A1>B1,"A" ...

  7. threejs三维地图大屏项目分享

    这是最近公司的一个项目.客户的需求是基于总公司和子公司的数据,开发一个数据展示大屏. 大屏两边都是一些图表展示数据,中间部分是一个三维中国地图,点击中国地图的某个省份,可以下钻到省份地图的展示. 地图 ...

  8. XTDrone和PX4学习期间问题记录(一)

    XTDrone和PX4学习期间问题记录(一) Written By PiscesAlpaca 前言: 出现问题可以去官方网站http://ceres-solver.org/index.html查看文档 ...

  9. Android Studio 卡在download fastutil下载慢

    需要替换国内镜像,现在阿里云地址已经更新了.需要使用新的地址.可以参考 https://developer.aliyun.com/mvn/guide 以下是更改buil.gradle文件的代码 // ...

  10. <一>继承的基本意义

    1:继承的本质和原理 2:派生类的构造过程 3:重载,覆盖,隐藏 4:静态绑定和动态绑定 5:多态,vfptr,vftable 6:抽象类的设计原理 7:多重继承以及问题 8:虚基类 vbptr 和v ...