嗨,技术朋友们!我已经有一段时间没有写东西了。我已经有一段时间没有写东西了。2020年是好的,2020年。不管怎么说,我开始在Bluecore工作了!我的任务是领导前台。因此,我一直在教一大堆人关于网络的知识。
如果你不知道,我玩弄网络已经有二十多年了。我是在2000年左右的某个时候开始的,当时还是个孩子。我记得在HTML 4还是全新的时候就开始学习它。在十几岁的时候,我为当地企业建立了网站。
我在2012年开始进入这个行业,为Lou Montulli工作,从那时起,我一直在网络上做这样或那样的事情。
当我在新的工作中得到夯实的时候,我一直在想我们花时间配置的工具数量之多。我一直在为前端架构写提案,并为北斗星铺设了一个愿景。
在这个过程中,我意识到网络上的这些背景是多么重要。知道我们从哪里来可以帮助我们弄清楚我们应该去哪里。这也是一座技术债务的大山,而我们正集体地在它上面建造。
请原谅我跳过Macromedia Flash、浏览器中的Java或其他你能想到的迂回的精彩故事。虽然这些对网络的发展很重要,但我们大多数人都不会再碰到它们了。
回到基础知识
第一个超文本标记语言(Hyper Text Markup Language,HTML)规范于1993年发布,作为表示网页的一种方式,然后是文档。最初,HTML是完全静态的,需要重新加载整个页面才能改变。
<!-- An entire web page could look like this: -->
<form action="/api/doctor">
<div class="row">
<label for="actor">Actor: </label>
<input id="actor" placeholder="David Tennant"/>
</div>
<div class="row">
<label for="companions">Companions: </label>
<input id="companions" placeholder="Rose, Martha, Donna"/>
</div>
<button type="submit">Submit</button>
</form>
<!-- Note: Many of these tags were unavailable in HTML 1.0 -->
JavaScript 出现在1995年,提供了一种处理事件的弱类型脚本语言。它只是偶尔用来改变页面的内容。
CSS出现于1996年,提供了一种新的方式来声明网站的外观和感觉,与文档中的主要内容分开。
对于动态呈现的页面,我们会使用像**PHP(1995年)**这样的语言,它可以根据请求提供一个服务器生成的HTML文档。JavaScript大多被洒在其中,以增加页面的互动性。

Mosaic,最早的浏览器之一
DOM
1998年 , 文档对象模型(DOM)被引入,事情才真正变得有趣。 DOM 是一个跨平台和独立于语言的接口,它将XML或HTML文档视为一个树状结构。每个节点都是一个代表文档一部分的对象。(感谢维基百科)
这为基于JavaScript的应用程序提供了一个跨浏览器的_基本一致_的接口,以便对浏览器中显示的内容进行修改。在DOM之前,我们有类似的基于树的结构,但它们还没有在各浏览器之间实现标准化。因此,API是非常不一致的。函数getElementById ,直到2000年才被标准化。键盘事件处理直到2004年才成为标准。

s/Websites/Web Applications/g
在21世纪初,网站开始从简单的页面走向复杂的应用。一些最引人注目的首批网络应用是Myspace(2003年)、Gmail(2004年)、Digg(2004年)、Google Maps(2005年)。
这些是最早使用AJAX(异步JavaScript和XML)的一些应用。这是网络的一个分水岭,因为我们终于能够在不重新加载整个页面的情况下更新一个应用程序。
每个浏览器对JavaScript规范ECMAScript(1997) 的实施都略有不同。 另外,如果你一定要知道,它以前是欧洲计算机制造商协会,但现在它只是Ecma。
这意味着应用程序开发人员不断地编写这样的黑客程序。
if (isIE()) {
// ie.stuff()
} else {
alert("Sorry, please use Internet Explorer");
}
你可以打赌,// ie.stuff() ,在一个if else块内有一千行代码。

浏览器历史
为了了解网络是多么的分散,我们需要讨论一些浏览器的历史。
1994年,网景公司是最初的市场领导者。

网景公司展示了Ask Jeeves的主页。
1996年,Internet Explorer通过其插件架构ActiveX获得了相关性。

在很长一段时间内,Internet Explorer是明显的领导者。它的市场份额直到2010年才低于50%!

摘自Statista
甚至Mac也运行IE浏览器,直到2003年苹果发布Safari。

Mac OS上的Safari 1.0
Internet Explorer的消亡始于2004年Netscape作为Mozilla Firefox的重生。火狐被认为比IE浏览器安全得多,并包括标签和集成的弹出式窗口阻止器等功能。

Windows XP上的Mozilla Firefox 1.0
我们直到2008年才看到Chrome浏览器--有趣的是,谷歌创建一个浏览器被认为是一件大事。

有一段时间,我们有四种浏览器拥有有意义的市场份额。这意味着网络必须在开源的ECMAScript上实现标准化,而不是专有的东西。
网络2.0
当所有这些关于浏览器的废话发生时,应用程序的复杂性也在上升。Web2.0带来了对互动性的新期望。用户不再是静态生成的网站,而是上传他们自己的内容。这需要大量的客户端业务逻辑,而这在以前是不必要的。
我最喜欢的两个Web2.0时代的网站是Digg和Meebo。
Digg是最早的社会新闻网站之一,以用户提交的故事和加注为特色。它甚至还衍生出了最早的播客之一,Diggnation。

Meebo是一个流行的信息应用,允许用户在任何地方同时登录多个信息服务。

jQuery
我们现在有越来越多的功能要求建立在一个没有人同意的语言规范上。这意味着应用开发者只能自己处理。
jQuery把这个问题抽象化了。与其为两三个浏览器写不同的代码,不如用一个简单的库来写。jQuery 1.0的代码量相当大,但我们还不关心包的大小,而且我们需要我们的东西能够工作。
代码看起来像这样。
var doctor = $('.doctor').attr('data-actor').value()
if (doctor === "David Tennant") {
callRoseTyler();
} else if (doctor === "Matt Smith") {
callAmyPond();
}
此外,它提供了一个简单的接口,用于对服务器进行API请求,现在我们可以做一些事情,比如提出API请求,并对这些请求发出回调。
这是革命性的,并开启了下一波复杂的网络应用。下面是其中一个应用程序的源码可能是这样的。
var companions = [];
var enemies = [];
var number = 10;
$.get("/api/companion/1", function (companion) {
$('.name', companion.name); // Rose Tyler
$('.seasons', companion.seasons); // Seasons 1 and 2
companions.push(companion);
});
$(document).on("click", ".display_tardis", function (e) {
e.preventDefault();
$(".container").html("<img src=/tardis />");
});
// Another thousand lines of code that looks like this.
我们现在在打API,存储状态,并改变页面标记--有时是在一个按钮的响应下。随着这些应用程序规模的扩大,我们必须弄清楚如何提供一些结构。从那里,我们开始看到MVC框架。
MVC
模型-视图-控制器模式在后端广泛流行,把它带到前端似乎是合乎逻辑的。大多数工程师已经习惯于使用后端MVC框架,他们可以用前台的工作方式来推理。
两个最流行的前端框架是Backbone和Angular。
在MVC的世界里,我们有一个存储数据的模型,一个展示数据的视图,以及一个用于业务逻辑的控制器。MVC是面向对象的,不同的对象有不同的功能。例如,模型可以与REST api同步它们的状态。
旁白:我们曾有一段时间集体真正进入了完整的REST模式,但我们现在大多回到了GET和POST。
这些框架在很大程度上是现代React生态系统的先驱。它们的特点是内置模板语言,并将更新DOM的逻辑和操作应用程序的状态分开。
尽管如此,状态管理仍然是困难的。不同的模型之间经常不同步,强制性的DOM突变意味着大量的竞赛条件。
捆绑大小
这时,包的大小成为一个问题。虽然依赖性不是很大,但我们在上面写了很多代码。我们也没有始终如一地提供经过压缩的文件,甚至没有经过压缩的文件。在服务器上来回奔波以提供一些JS,然后再获取更多的JS,这样做也很昂贵。
为了解决这个问题,我们需要将文件串联起来以减少请求,将其最小化以减少文件大小,并将其gzip化以减少网上的大小。 这通常会将有效载荷的大小降低5倍。
然而,客户仍然需要对其进行解析。几年前,我对这个问题进行了分析,每一个未压缩的千字节的javascript需要大约1毫秒的时间来解析它并首次运行它。即使是现代的框架,每个文件都必须在页面互动之前至少执行一次,以便把所有的功能都放到位。
不管怎么样。我们专注于减少通过电线发送的代码量。这可能意味着缩小它,或者优化我们加载它的时间。重要的是要记住,在这之前,我们会单独为每个未减化的源文件服务。
**我们就是这样开始迷恋上了工具化。**在发货之前,我们需要对我们的代码做一系列的事情。我们需要将源文件串联成按功能分组的包,对其进行最小化处理,并在文件名中加入哈希值以破坏缓存。这变得很复杂,这导致了整个工具的生态系统:npm、grunt、gulp,以及最终的webpack。
语言
在我们引入构建步骤之后,我们就可以开始调整语言本身了。2009年,CoffeeScript被发布。这是一种全新的语言,可以编译成JavaScript,并具有箭头函数、可选链和现代字符串插值等新功能。
就像jQuery进入了现代DOM应用程序一样,CoffeeScript进入了JavaScript。它的许多新功能都进入了ECMAScript,而我们也从ECMAScript中走了出来。
此外,我们还有像underscore和lodash这样的库来影响JavaScript。ES6在语言中为我们提供了map、filter和reduce。这使得这些库的大部分都被淘汰了。
在2014年,6to5(后来的babel)被引入。这使我们能够使用新的ES6(ECMAScript 6)规范编写源代码,而不放弃对旧浏览器的支持。它通过将较新的ES6转译为ES5来工作。
这是网络有史以来最大的发展之一。能够改进语言而不用担心旧的浏览器。ES6的开发被加速了,TC39能够转向每年发布一次。
CSS和HTML也在不断发展。HTML 5在2014年发布,带来了一整套我们可以实现的新标签。CSS出现了新的方言,如SASS(2006年)和LESS(2009年),并得到了我们用于JavaScript的相同构建工具的支持。
差不多了
我们已经达到了这样的地步:JavaScript生态系统看起来很像现代网络。让我们来看看2015年的一个非常常见的堆栈。
- 语言。E S6是新的,CoffeeScript仍在大量使用。新的代码库正在使用babel,但许多仍然是CoffeeScript或甚至ES5。
- 库: jQuery正在演变为每个人最喜欢的技术债务。Lodash仍然非常流行。
- 工具。 像Grunt和Gulp这样的构建工具在很大程度上是任务运行者。一个是重配置,另一个是重代码。
- 性能。服 务的速度变快了,但我们仍然在用手做DOM突变,这很慢而且容易出错。
- 风格设计。 LESS和SASS在当时是非常不错的。
模块
我们还没有讨论模块。在很长一段时间里,我们会通过一系列的<script /> 标签将JavaScript包含在页面中,并确保以正确的顺序加载它。
有一段时间,有两个相互竞争的JavaScript模块化标准/工具:commonJS和AMD(异步模块定义)。ESModules直到2018年才成为标准。
还有一些工具在2009-2013年之间出货,最引人注目的是requireJS。这些工具提供了一种机制,用于隔离JavaScript和声明项目中的依赖关系。
问题是,它的编写是一场噩梦。以下是使用 requireJS 的标准文件的情况。
requirejs(["jQuery", "underscore", "backbone"], function($, _, Backbone) {
// This function is called when all of its dependencies have loaded.
// Above, we have a 1:1 mapping of path to module export.
});
这些require语句变得足够大,它们被手工写了很久。
Webpack在2014年首次发布,它是第一个将构建和依赖性管理合二为一的主流工具。它包括新的import 语句,它可以编译成任何必要的require 语句。
我们甚至还没有讨论Node,但在这个时候,同构的JavaScript开始流行(在客户端和服务器上运行相同的代码)。
React
似乎是突然出现的,Facebook在2013年底发布了React。当时的网络开发现状有很多问题,React旨在解决这些问题。
- 我们将业务逻辑与DOM突变混在一起。
- 我们在实际的DOM节点上存储数据。
- 对DOM的读/写很昂贵。
- 状态管理是一场噩梦,所有的东西都在不断地与所有东西不同步。
2014年,Facebook发表了一篇题为《黑客之路 》的传奇性演讲_。重新思考Facebook的Web应用开发。_
这个演讲详细介绍了他们如何引入React并采用Flux模式来简化Web开发。React做出了一个简单的承诺,UI是状态的函数。
Flux模式的工作原理如下。我们有一个位于应用程序顶部的数据存储,React将从该数据存储中读取并渲染UI,每当它需要改变状态时,它将发布一个更新数据存储的动作。这将触发整个应用程序的重新渲染。

摘自 Redux文档
但这是有魔力的!React引入了虚拟DOM,有效地消除了强制性的DOM变异问题。
React提供了一个编写声明性组件的接口。这意味着你告诉React_拿这个状态并把它变成这个UI。_ 它不做的是告诉你如何从(状态A,用户界面A)到(状态B,用户界面B);React在后台做了所有这些。
React会对状态A做一个完整的渲染,随后它将对状态B做一个完整的渲染,然后它将在两个虚拟DOM(而不是缓慢的真实DOM)上做一个差异,并确定对DOM所做的最小变化集。
React还引入了一种叫做JSX的语法,这是一种直接在JavaScript模块中编写类似html的标记的方法。这使得开发者可以在标记旁边创建具有业务逻辑的组件,而不需要在代码中实际放入HTML字符串。
这使得应用程序更快、更可预测,对所有人来说都是一种胜利。最终,Redux成为最流行的通量实现。这导致了函数式编程在网络上的流行。
下面是一些React代码可能看起来像什么。
import React from "react";
function TheDoctor({ number, actor, companions }) {
return (
<div>
<h1>The {number} Doctor</h1>
<h2>Played by {actor}</h2>
<div className="companions">
{companions.map((companion) => (
<Companion {...companion} />
))}
</div>
</div>
);
}
<TheDoctor
number="Twelfth"
actor="Peter Capaldi"
companions={["Clara Oswald", "Nardole", "Bill Potts"]}
/>;
TypeScript
多年来,JavaScript不断变得更好。我们从ES6这样的大版本转向ES2021这样的年度迭代版本。有人添加类型是很有意义的。
TypeScript是一种静态类型的语言,可以编译成传统的JavaScript。它在编译时执行类型检查,但运行时的代码仍然是无类型的。尽管这并不完美,但也足够好用。
虽然TypeScript在2008年首次发布,但它直到十年后的2019年才被主流采用。据我所知,Dropbox是最早在2015年大规模采用TypeScript的公司之一,或许我只是为我们的迁移工作感到非常自豪。
TypeScript在2.x阶段开始变得不错。这些版本带来了严格的空值检查、异步函数、枚举、Pick ,以及我们今天认为理所当然的一系列实用工具。从那时起,它变得越来越强大,并迅速上升为最流行的Javascript方言。
随着类型的出现,我们现在可以在代码库的不同部分之间执行契约。
type DoctorProps = {
number: string;
actor: string;
companions: string[];
}
function TheDoctor({ number, actor, companions }: DoctorProps) {
// Function Body
}
<TheDoctor
// Type 'number' is not assignable to type 'string'. ts(2322)
number={13}
actor="Jodie Whittaker"
companions={["Graham", "Ryan", "Yasmin"]}
/>
如果你感到好奇,我曾经做过一个关于TypeScript为何如此神奇的演讲。
技术债务之山
所有这些历史,我们已经达到了2021年的web开发。我们也跳过了很多。GraphQL,CSS的进步(模块、风格化组件等),以及整个节点生态系统。
如果从所有代码都是技术债务的角度来看,那就是一座山。我们仍然在使用与20年前基本相同的工具构建网络应用。JavaScript、HTML和CSS。
它们已经有了相当大的发展,但它们继续保持对有史以来每个网站的支持。此外,任何足够大的代码库都会包括来自其过去的工件。有多少人声称自己在运行TypeScript+React,但暗地里却有一些骨干,一些jQuery,或者一些经过检查的脱咖啡因的CoffeeScript?而这只是我所能想到的东西。
即使有了ES2021的最新功能,绝大多数的网站仍然以ES5为目标,这是一种2009年发布的语言。我们可以添加类型和声明式运行时,但它最终会在无类型和命令式环境中执行。
我们对工具的痴迷已经达到了新的高度。让我们来看看现代应用程序的一个相当标准的设置。
- TypeScript - 增加了对类型和较新的ES功能的支持。通常可以编译到ES5。
- React 17 + React DOM - 约 35kb的gzipped运行时间,允许声明式编程。
- Apollo - 流行的用于API请求的graphql库。约33kb gzipped。
- NPM - 包裹管理器,支持拉入第三方依赖(包括这里列出的那些)。
- Webpack - 构建工具,运行底层的TypeScript编译器并处理最小化、捆绑分割等。
- ESLint - 检查代码的样式和潜在的运行时错误。
- Prettier - 代码格式化器,标准化空格、引号和分号。
- CSS Modules - 允许在JavaScript文件中直接导入CSS。
- Jest - 单元测试运行器。
- Cypress - 端到端的测试运行器。
这就是很多工具了!今天的网络开发者需要了解所有这些工具,它们如何相互作用,以及它们如何与所有那些不会消失的_遗留_代码相互作用。
这个清单还在继续,最糟糕的是,所有这些工具都是用JavaScript编写的,这导致了构建时间的延长,测试运行时间的延长,以及一个拖沓的反馈循环。
事实上,最近在性能方面的大部分发展都是通过用其他语言编写JavaScript工具来实现的。esbuild团队通过用rust编写bundler,使其性能比webpack 5提高了150倍。

页面加载
这只是在我们的终端!即使我们修复了这些工具,我们仍然要将代码下发到浏览器。下面是一个普通的页面负载的样子。
- Http请求进入服务器
- 服务器提供初始HTML标记
- HTML被扫描,
<script />标签被加载。 - JavaScript通过电线下来,被解析并首次执行。
- JavaScript代码通过JSON API对内容进行网络请求。
- API请求回来了,JavaScript渲染了页面,页面最终是互动的。
- 懒惰加载的代码开始加载。
- 在我们获取和渲染代码/内容的过程中,模块突然出现在页面中。
在这里,我们可以做大量的性能优化,但在一天结束时,我们必须以时间或复杂性的形式支付这一成本。我们所做的任何性能优化都会使服务更加复杂,并为我们的东西创造更多的故障点。
未来
我觉得最有趣的两个项目是Svelte和Deno。React的竞争对手有很多,最明显的是Vue和Angular 2。虽然他们有自己的应用开发方法,但他们仍然将运行时下放到浏览器中。
Svelte则不同,它没有运行时。它的所有工作都是在编译时完成的,因此你可以尽可能少地发送代码。虽然Svelte还没有被有意义地大规模采用,但我相信无论什么东西从React手中接过王位,都不会有运行时。
Deno是TypeScript的一个安全运行时间。它建立在Rust中,完全彻底改变了我们在服务器中运行V8的方式。虽然它仍然运行V8,但他们在很大程度上对用户进行了抽象,允许在未来将其替换掉。
我相信我们已经找到了网络开发的正确接口。TypeScript和React提供了一个令人难以置信的开发者体验,他们是建立在这些限制之上的。如果我们把TypeScript和React拿出来,老老实实地把它编译好,会怎么样?如果我们能把React运行时作为浏览器的一部分来运行呢?如果我们在运行时利用类型数据的优势呢?
如果我们开始这样做,我们将采用现有的前端工程师熟悉的范式,但要对其实施进行涡轮增压。在那之前,我们将继续在1998年的环境中执行我们的代码。