大多数 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. Netty 学习(十):ChannelPipeline源码说明

    Netty 学习(十):ChannelPipeline源码说明 作者: Grey 原文地址: 博客园:Netty 学习(十):ChannelPipeline源码说明 CSDN:Netty 学习(十): ...

  2. Linux Block模块之IO合并代码解析

    1 IO路径 从内核角度看,进程产生的IO路径主要有三条: 缓存IO:系统绝大部分IO走的这种形式,充分利用文件系统层的page cache所带来的优势.应用程序产生的IO经系统调用落入page ca ...

  3. 5.MongoDB系列之索引(二)

    1. $运算符如何使用索引 1.1 低效的运算符 $ne.$not查询可以使用索引,但不是很有效,尽量避免 1.2 范围查询 范围查询其实是多值查询,根据复核索引规则,尽可能先等值精确匹配,然后范围查 ...

  4. hyperf-搭建初始化

    官方文档* https://hyperf.wiki/2.0/#/README 初步搭建1. 安装项目 composer create-project hyperf/hyperf-skeleton 2. ...

  5. MySQL开发

    常用数据类型 整数:tinyint.int.bigint小数:decimal.字符串:char.varchar.text 增 insert into 表名(列名,列名)values(值,值): 删 d ...

  6. C#中进行数值的比较

    Equals的使用 str1.Equals(str2,StringComparison.OrdinalIgnoreCase);     ----比较str1和str2       StringComp ...

  7. 浅入浅出 1.7和1.8的 HashMap

    前言 HashMap 是我们最最最常用的东西了,它就是我们在大学中学习数据结构的时候,学到的哈希表这种数据结构.面试中,HashMap 的问题也是常客,现在卷到必须答出来了,是必须会的知识. 我在学习 ...

  8. Sublime Text 修改默认语言为Python

    Sublime Text 3 修改默认语言为Python 步骤如下 英文:Tools - Developer - New Plugin 中文:工具 - 插件开发 - 新建插件 清空原来内容,用下面的代 ...

  9. 学习ASP.NET Core Blazor编程系列十——路由(上)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  10. ubuntu 基本指令

    系统相关 df: disk free 用以显示系统上文件系统磁盘的使用情况 # 以M/G单位显示硬盘空间大小 df -h apt: advanced packaging tool 包管理工具 apt ...