Web应用程序加载简史
最初,只有<script>标签。
我们之前通过在 HTML 中精心组织脚本来管理依赖关系。你必须先加载 jQuery,然后才能加载插件;先加载库,然后才能加载应用程序代码。随着我们添加更多交互功能,并从网站发展到 Web 应用程序,这种方式开始变得难以掌控。大型项目会产生复杂的请求瀑布,难以管理和优化。我们虽然使用了依赖项defer和async属性,但它们只在某些情况下有用。我们需要一种更好的方法来管理依赖关系。
第一个进步是开始将脚本连接起来。这减少了HTTP请求的总数,并有助于保证执行顺序,但这仍然是一个手动过程。脚本必须按正确的顺序连接才能正常工作。我们将脚本分组连接,以平衡每个文件的大小和请求总数,但我们仍然需要指定顺序和分组。大约就在这个时候,为JavaScript添加构建步骤的概念开始流行起来。
Grunt成为第一个广受欢迎的“任务运行器”,用于连接脚本和优化资源。然而,在大型项目中,它的配置变得过于繁琐,Gulp则将这些理念提炼成一个更易于理解且速度更快的“流式”API。
随着我们逐渐接受构建步骤的概念,CoffeeScript作为第一个流行的替代语法应运而生。由于当时有大量的应用程序是用 Ruby on Rails 编写的,Web 开发人员渴望使用 Ruby 更简洁的语法。CoffeeScript 的许多理念最终被 ES2015 所吸收——例如,`<script>`=>和`<script>` 标签...等。它还帮助推广了将代码拆分为模块的概念。每个编译后的 CoffeeScript 文件都会被插入到其自身的立即实例化函数表达式 (IIFE) 中,从而限制了每个脚本的作用域,防止污染全局命名空间。
Require.js和Bower 的出现帮助我们管理第三方代码。Require.js 引入了“异步模块定义”(AMD 模块),这种打包方法至今仍被一些应用程序使用。它们按需加载到浏览器中,这真是太棒了!再也不用手动调整 script 标签了。不过,它的语法略显笨拙。
// from http://requirejs.org/docs/api.html
requirejs(['jquery', 'canvas', 'app/sub'],
function($, canvas, sub) {
//jQuery, canvas and the app/sub module are all
//loaded and can be used here now.
});
但这比我们手动管理订单要好得多。Bower 最初是 npm 的补充,当时 npm 还没有很多支持在浏览器中运行的模块。最终,Bower 被弃用,npm 取而代之,而 Require.js 则增加了传递 require 函数的功能,以模拟来自 Node 的 CommonJS 模块。
define(function(require, exports, module) {
var $ = require('jquery');
var canvas = require('canvas');
var sub = require('app/sub')
})
现在我们有了能够自动管理加载哪些脚本以及加载顺序的工具。一切都很顺利。然而,新的问题逐渐出现:添加依赖项太容易了,我们开始大量使用依赖项。由于每个依赖项都作为单独的脚本加载,加载一个 Web 应用会触发数十甚至数百个 HTTP 请求,请求的是一些很小的 .js 文件。这些同时发生的请求会相互阻塞,导致初始加载延迟。
针对这个问题,人们开发了多种解决方案。HTTP2 的设计也考虑到了这个问题,并加入了多路复用技术来缓解它。Require.js 添加了一个优化工具,可以将这些模块打包成单个文件或文件组,但它并不适合开发环境,而且配置起来也很复杂。HTTP2 的推广速度非常缓慢,最终也未能像人们期望的那样成为万能的解决方案。
开发者们开始尝试各种替代方案,用于打包依赖项的工具数量也随之激增。Browserify、Broccoli.js、Rollup、webpack,当然还有一些我从未听说过的工具。而且,新的工具还在不断涌现,Parcel 是我所知的最新成员。它们在 API 和功能方面都略有不同。webpack 凭借其出色的代码分割功能和灵活性赢得了应用开发者的青睐,后续版本更是显著提升了易用性(说真的,webpack 4 非常棒)。Rollup 则成为了打包库的首选工具,因为它在大多数情况下都能生成最小的包。
对依赖项解析工具的关注暴露了 CommonJSrequire函数的一些缺陷。CommonJS的依赖项解析功能require最初是作为 Node.js 的一部分创建的,其语义特性使其在浏览器中使用起来较为困难。TC39 制定了 ES 模块定义规范,更好地满足了 Node.js 和浏览器中不同的使用场景。ES 模块仍在不断发展完善——Node.js 最近发布了包含实验性支持的 10 版本,但动态依赖项解析import()功能尚未完全实现。
这就引出了我们今天的话题。Webpack 多年来一直是事实上的打包工具,并且一直在稳步改进。我们不仅可以定义 JavaScript 包,还可以指定哪些文件依赖于样式表或图片,并仅在需要时加载它们。加载器可以内联小于特定尺寸的图片,一些大胆的开发者甚至开始在 JS 中编写 CSS(不妨一试,效果很棒)。
我甚至还没提到 Yarn、npm 和 pnpm 之间的区别,也没提到 unpkg 之类的服务,更没提到那些导致我们走到今天这一步的种种争论和纷争。npm在 2016 年每周下载量突破 10 亿之后,便一路飙升,而2018 年初的数字更是远远超过了这个数字。我们现在面临的挑战在于何时应该避免使用依赖项,以及如何控制我们发布的代码总量。
这只是我过去六年编写浏览器代码的亲身经历的概括。虽然在互联网发展史上这只是很短的一段时间,但其创新和演变之迅速令人惊叹。
感谢阅读!我的推特账号是@cvitullo(其他平台账号是 vcarl)。我管理着Reactiflux(React 开发者聊天室)和Nodeiflux(Node.js 开发者聊天室)。如果您有任何问题或建议,欢迎联系我!
文章来源:https://dev.to/vcarl/a-brief-history-of-web-app-loading-5d6a
