错过校招
by Harnoor Bandesh
由Harnoor Bandesh
您可能错过的Web优化技巧 (The Web Optimization trick you might have missed)
Have you ever wondered why the Google search pages or Amazon site loads really fast? Well stay with me while I take you through the concept, which drastically improves page performance. But firstly, let us go through some concepts that lead to the idea.
您是否曾经想过为什么Google搜索页面或Amazon网站加载速度如此快? 在我为您介绍概念时,请与我在一起,这将大大提高页面性能。 但是首先,让我们仔细研究一些导致该想法的概念。
分析关键渲染路径(CRP) (Analysing Critical Rendering Path (CRP))
First of all, let us define the vocabulary that we will be using:
首先,让我们定义将要使用的词汇:
Critical Resource: Resource that could block initial rendering of the page
关键资源 :可能阻止页面初始呈现的资源
Time To First Byte (TTFB): Measures the duration from the browser making an HTTP request to the first byte of the page being received by the browser
第一个字节的时间(TTFB) :测量从浏览器发出HTTP请求到浏览器接收到页面的第一个字节的持续时间
Optimizing web performance is all about understanding what happens in the intermediate steps between receiving the HTML, CSS, and JavaScript files and the required processing to turn them into rendered pixels — that’s the critical rendering path (CRP).
优化Web性能就是要了解在接收HTML,CSS和JavaScript文件以及将它们转换为渲染像素所需的处理之间的中间步骤中发生的事情-这是关键的渲染路径(CRP)。
Before the pages are rendered, the browser has to go through all the following steps:
在呈现页面之前,浏览器必须完成以下所有步骤:
When the browser first hits the page, it downloads the HTML file. It then starts building up the DOM (Document Object Model) tree. Each tag in HTML represents a node inside the DOM tree which has all the information about it. Let us take an example to understand this.
当浏览器第一次访问该页面时,它会下载HTML文件。 然后,它开始构建DOM(文档对象模型)树。 HTML中的每个标记代表DOM树中的一个节点,该节点具有有关它的所有信息。 让我们以一个例子来理解这一点。
Suppose the Browser receives the following HTML from the server:
假设浏览器从服务器接收以下HTML:
<html>
<head>
<meta name="viewport" content="width=device-width,initial- scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
Browser converts it into a tree object called the DOM as:
浏览器将其转换为名为DOM的树对象,如下所示:
Note: The DOM Construction Process is incremental. This is the basis of the idea for which I am writing this article.
注意 :DOM构建过程是增量的。 这是我撰写本文所基于的想法的基础。
While the browser was constructing the DOM, it encountered a link
tag in the head
section referencing an external CSS stylesheet.
当浏览器构造DOM时,它在head
部分遇到一个引用外部CSS样式表的link
标记。
Anticipating that it needs that resource to render the page, it dispatches a request for the same, which comes back with the following content:
预期将需要该资源来呈现页面,因此调度了对页面的请求,该请求返回了以下内容:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
The browser then creates the CSSOM (CSS Object Model):
然后,浏览器创建CSSOM(CSS对象模型):
The CSSOM and DOM trees are combined to form a Render tree. The Render tree is then used to compute the layout of each visible element.
CSSOM和DOM树被合并形成一个Render树。 然后,将“渲染”树用于计算每个可见元素的布局。
This is what a Render tree looks like:
这是渲染树的样子:
Some nodes are not visible — such as script tags and meta tags — and are omitted since they are not reflected in the rendered output. Some nodes are hidden via CSS and are also omitted from the render tree.
一些节点是不可见的(例如脚本标签和元标签),由于它们未反映在渲染的输出中,因此将其省略。 一些节点通过CSS隐藏,并且在渲染树中也被省略。
Now with the Render tree in place, we can head for the Layout step. The output of the layout process is a “box model” in which the exact position and size of each element is captured. All the relative measurements are converted to absolute pixels on the screen.
现在有了“渲染”树,我们可以进入“布局”步骤。 布局过程的输出是一个“盒子模型”,其中捕获了每个元素的确切位置和大小。 所有相对测量值都将转换为屏幕上的绝对像素。
Finally, now that we know which nodes are visible, and their computed styles and geometry, we can pass this information to the final stage. This stage converts each node in the Render tree to actual pixels on the screen. This step is often referred to as “painting”.
最后,既然我们知道了哪些节点是可见的,以及它们的计算样式和几何形状,我们就可以将此信息传递到最后阶段。 此阶段将“渲染”树中的每个节点转换为屏幕上的实际像素。 此步骤通常称为“绘画”。
Note: the CSS is render blocking. Until the CSSOM is constructed, the browser cannot proceed to the Render tree step. Hence we need to serve the CSS file to the browser as soon as possible, which is why we keep all the link
tags in the head
section.
注意 :CSS被渲染阻止。 在构造CSSOM之前,浏览器无法继续执行“渲染树”步骤。 因此,我们需要尽快将CSS文件提供给浏览器,这就是为什么我们将所有link
标签都保留在head
。
Now let us add JavaScript to our example:
现在让我们将JavaScript添加到示例中:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
<script src="app.js"></script>
</body>
By default, JavaScript execution is “parser blocking”. When the browser encounters a script
tag in the document, it does the following steps:
默认情况下,JavaScript执行是“解析器阻止”。 当浏览器在文档中遇到script
标签时,它将执行以下步骤:
1. Pause DOM construction
1.暂停DOM构建
2. Download the file
2.下载文件
3. Hand over control to the JavaScript runtime
3.将控制权移交给JavaScript运行时
4. Let the script execute before proceeding with DOM construction
4.在继续进行DOM构建之前,让脚本执行
The browser does not know what the script is planning to do on the page, so it assumes the worst case scenario and blocks the parser.
浏览器不知道脚本计划在页面上执行的操作,因此它假定最坏的情况并阻止了解析器。
Hold on!!! This is not the worst case that can happen while DOM parsing. In the last example, we can see that we have both CSS and JavaScript external files that the browser needs to download.
坚持,稍等!!! 这不是DOM解析时可能发生的最坏情况。 在上一个示例中,我们可以看到浏览器需要下载CSS和JavaScript外部文件。
Now, suppose the CSS files take some time to download, and in the meanwhile the JavaScript file gets downloaded. Now, the browser will assume the worst case scenario that JavaScript might query CCSOM, which is why it does not start parsing the JavaScript file until the CSS file is downloaded and CSSOM is ready!
现在,假设CSS文件需要花费一些时间下载,并且同时下载了JavaScript文件。 现在,浏览器将假定JavaScript可能查询CCSOM的最坏情况,这就是为什么在下载CSS文件并且CSSOM准备就绪之前,它不会开始解析JavaScript文件的原因!
Let us look at a diagram which might help us to get a better understanding of what I am trying to say:
让我们看一个图表,它可以帮助我们更好地理解我要说的话:
CSS is a demon for any web page! It is render blocking and parse blocking as well. We need to be very careful in handling it.
CSS是任何网页的恶魔 ! 它也是渲染阻止和解析阻止。 我们在处理它时需要非常小心。
Let us look into some ways to optimize the CRP.
让我们研究一些优化CRP的方法。
优化CRP (Optimizing the CRP)
At this point we know that the CSS can be a demon. Get it to the client as soon as possible to optimize the time to first render. What if we have some CSS styles that are only used under certain conditions? For example, when the page is being printed or being projected onto a large screen?
至此,我们知道CSS可能是恶魔。 尽快将其提供给客户端,以优化首次渲染的时间。 如果我们有一些仅在特定条件下使用CSS样式怎么办? 例如,何时打印页面或将页面投影到大屏幕上?
It would be nice if we didn’t have to block rendering on these resources. CSS “media types” and “media queries” allow us to address these use cases:
如果我们不必阻止这些资源的渲染,那就太好了。 CSS“媒体类型”和“媒体查询”使我们能够解决以下用例:
<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
A media query consists of a media type which checks for the conditions of particular media features. For example, our first stylesheet declaration doesn’t provide a media type or query, so it applies in all cases. That is to say, it is always render blocking.
媒体查询由一种媒体类型组成,该媒体类型检查特定媒体功能的条件。 例如,我们的第一个样式表声明不提供媒体类型或查询,因此它适用于所有情况。 也就是说,它总是渲染阻塞。
On the other hand, the second stylesheet declaration applies only when the content is being printed. Hence this stylesheet declaration doesn’t need to block the rendering of the page when it is first loaded.
另一方面,第二个样式表声明仅在打印内容时适用。 因此,此样式表声明在首次加载时不需要阻止页面的呈现。
Finally, the last stylesheet will be executed by the browser if the condition matches. If the condition does not match, the browser wont block the rendering.
最后,如果条件匹配,最后的样式表将由浏览器执行。 如果条件不匹配,则浏览器将不会阻止渲染。
When declaring your style sheet assets, pay close attention to the media type and queries. They greatly impact critical rendering path performance.
在声明样式表资产时,请特别注意媒体类型和查询。 它们极大地影响了关键渲染路径的性能。
By default all JavaScript is parser blocking. A signal to the browser that the script does not need to be executed at the point where it’s referenced allows the browser to continue to construct the DOM and lets the script execute when it is ready. For example, after the file is fetched from cache or a remote server.
默认情况下,所有JavaScript都是解析器阻止。 向浏览器发送信号,表明该脚本无需在被引用时执行,从而使浏览器可以继续构造DOM,并在脚本准备就绪时执行该脚本。 例如,从高速缓存或远程服务器中获取文件之后。
To achieve this, we mark our script
as async
:<script src=”app.js” async></script>
为此,我们将script
标记为async
: <script src=”app.js” async></script>
Adding the async
keyword to the script
tag tells the browser not to block DOM construction while it waits for the script to become available, which can significantly improve performance.
在script
标签中添加async
关键字可以告诉浏览器在等待脚本可用之前不要阻止DOM的构建,这可以显着提高性能。
One more plus point of the async
attribute is that the script
does not get blocked waiting for CSSOM to get ready.
async
属性的另一个优点是, script
不会在等待CSSOM准备就绪之前被阻止。
Analytics script is great example for the async
attribute as the script
does not changes the DOM in any way. There is one more attribute for script
tags, which is defer
. You can learn about defer
by visiting here.
Analytics脚本是async
属性的绝佳示例,因为该script
不会以任何方式更改DOM。 script
标签还有一个属性defer
。 您可以通过访问此处了解有关defer
的信息 。
And finally — the apex of the article arrives, where I will tell you the main secret — apart from the optimizations stated above — that big companies apply and do wonders…
最后-文章的顶点到了,在这里,我将告诉您一个主要秘诀-除了上述优化之外-大公司应用并创造奇迹...
从服务器分块发送HTML (Sending HTML in chunks from the server)
Look at the following images and decide: in which way would you want your websites to render?
查看以下图像并决定:您希望网站以哪种方式呈现?
Got an answer? It’s the first one of course! No one really like to see the blank page for so long. It is much better to render the HTML in chunks on the web page which is what Google Search pages, Amazon, and other big giants do.
有答案吗? 当然是第一个! 如此长的时间,没有人真的喜欢看到空白页。 最好在网页上分块呈现HTML,这就是Google搜索页,亚马逊和其他巨头的做法。
Now when you first hit the URL of any website, the complete HTML of the page gets built up on server. Until that time, the browser is sitting idle doing nothing.
现在,当您首次点击任何网站的URL时,页面的完整HTML就会在服务器上建立。 在此之前,浏览器一直处于闲置状态。
After the HTML is built on the server, it is passed on to the browser. The browser then starts building up the DOM and goes through all the CRP steps as mentioned before.
在服务器上构建HTML之后,会将其传递到浏览器。 然后,浏览器开始构建DOM,并按照前面所述完成所有CRP步骤。
The following diagram will help us get this thing clearer:
下图将帮助我们更清楚地了解这一点:
So why don’t we optimize the idle time of the browser and make it start building up the DOM by sending the HTML chunk that is ready to the server? In other words, we can flush out the HTML in chunks the moment they get ready, instead of waiting for the entire HTML to get prepared. This will make the browser start building up the DOM/CSSOM tree instead of waiting idle. Isn’t that a wonderful idea!
那么,为什么不优化浏览器的空闲时间,并通过将准备好HTML块发送给服务器来使其开始构建DOM呢? 换句话说,我们可以在准备就绪时立即将HTML分成几部分冲刷,而不必等待整个HTML做好准备。 这将使浏览器开始建立DOM / CSSOM树,而不是等待空闲。 这不是一个好主意!
I hope the following diagram may clear up this idea more:
我希望下图可以进一步澄清这个想法:
The page is divided into HTML chunks on the server. Now the server, instead of waiting for the entire HTML to get ready and then serving it to the browser, will send the HTML chunks the moment they are ready on the server. This means that the first chunks won’t wait for the other two to get ready — they will be served to the browser the moment they are ready on the server.
该页面在服务器上分为HTML块。 现在,服务器不再等待整个HTML就绪,然后将其提供给浏览器,而是在服务器上准备就绪时发送HTML块。 这意味着前几个块将不会等待其他两个块准备就绪,它们将在服务器上准备就绪后立即提供给浏览器。
Let us take an example to understand this idea even better. The following is the Google Search page:
让我们举个例子来更好地理解这个想法。 以下是Google搜索页面:
Now suppose we hit this URL, and the browser dispatches a request to the server to serve this page. The server starts building this page and has completed the HTML of Part A, but for Part B it needs to fetch the data from some source which will take some more time.
现在,假设我们点击了该URL,并且浏览器向服务器发送了一个请求以服务于此页面。 服务器开始构建此页面,并已完成A部分HTML,但是对于B部分,它需要从某个来源获取数据,这将需要更多时间。
Now, instead of waiting for part B to get completed, the server flushes out the completed HTML of part A to the browser so that it starts building up the DOM.
现在,服务器无需等待B部分完成,而是将A部分的完成HTML刷新到浏览器,从而开始构建DOM。
In the meantime, the server prepares the HTML of part B with the required data. In this way, the user would be able to see the web page loading progressively on the browser. Sending HTML in chunks also reduces the Time To First Byte and improves performance and page speed index of the page.
同时,服务器使用所需数据准备B部分HTML。 这样,用户将能够看到网页逐渐在浏览器上加载。 分块发送HTML还可以减少“第一个字节的时间”,并提高页面的性能和页面速度索引。
This is what Google is actually doing in their Search pages! Even Amazon throws their header first while the rest of the page gets prepared on the server.
这实际上是Google在其搜索页面上所做的! 甚至Amazon也会在页面的其余部分在服务器上准备就绪时首先抛出其标头。
Sending the HTML in chunks also serves one more purpose of optimization. As your head
tag reaches the client first, the browser initiates the CSS and other requests in the tag. This helps the browser download other critical resources while the rest of the HTML is prepared by the server.
分块发送HTML还可以达到优化的另一个目的。 当您的head
标签首先到达客户端时,浏览器会在标签中启动CSS和其他请求。 这有助于浏览器下载其他关键资源,而其余HTML由服务器准备。
Typical time to fetch a page from the server is around 500 ms. But a typical time to get the first chunk from the server is around 20–30 ms. The CSS call which was to be initiated after 500 ms will now be initiated after 20–30 ms, giving the web page a boost of around 470–480 ms. You can even preload
the heavy images in head
tag which will be used by the HTML that is yet to come from the server, improving page load time!
从服务器获取页面的典型时间约为500毫秒。 但是从服务器获取第一个块的典型时间约为20-30毫秒。 原本在500毫秒后启动CSS调用现在将在20–30毫秒后启动,从而使网页提升了约470–480毫秒。 您甚至可以将重磅图像preload
到head
标签中,该标签将被服务器尚未使用HTML使用,从而缩短了页面加载时间!
Now the question is: how to send HTML in chunks from the server side.
现在的问题是:如何从服务器端分块发送HTML。
Well, we have different ways in different languages. We have a method called flush
in Java, .NET and PHP. In Node.js we need to res.write()
whenever our HTML chunk is ready.
好吧,我们用不同的语言有不同的方式。 我们在Java,.NET和PHP中有一种称为flush
的方法。 在Node.js中,只要准备好HTML块,我们就需要res.write()
。
Note: The browser does not make repetitive calls to the server to get all the chunks. All the HTML chunks are served over a single call to the server.
注意:浏览器不会重复调用服务器来获取所有块。 所有HTML块均通过对服务器的一次调用提供。
我的POC (My POC)
I have made a POC with Node.js, Express and React, where the React components are rendered on Node.js and each component is flushed to the browser as soon as its HTML gets prepared. You can find the source code here.
我已经用Node.js,Express和React进行了POC,其中React组件在Node.js上呈现,并且每个组件在准备好HTML后都会刷新到浏览器中。 您可以在此处找到源代码。
You can see the live demo here.
您可以在此处观看现场演示。
In the demo you can see links. The Move to page without chunking link will take you the page in which the chunking concept has not been applied. The Move to page with chunking link will take you to the page in which the chunking concept has been applied. Below are screenshots of the page.
在演示中,您可以看到链接。 移至不带分块的页面链接将带您进入未应用分块概念的页面。 转到带有分块的页面链接将带您进入已应用分块概念的页面。 以下是页面的屏幕截图。
The page is divided into 4 parts. The moment Part A gets prepared on the server it is flushed to the browser so that the browser can start building up the DOM.
该页面分为4个部分。 一旦在服务器上准备好A部分,就将其刷新到浏览器,以便浏览器可以开始构建DOM。
Part B is built up using the data from an API which will take some time. Up until then the browser creates the HTML of Part A as a DOM construction in an incremental process.
B部分是使用API中的数据构建的,这将需要一些时间。 直到那时,浏览器在增量过程中将A部分HTML创建为DOM结构。
The moment the HTML of Part B gets prepared on the server, it is served to the browser. The story goes on for Part C and Part D.
在服务器上准备好B部分HTML之后,会将其提供给浏览器。 故事在C部分和D部分进行。
But here is one catch: even before sending the Part A, I send one more chunk to the browser which is the head
tag of the HTML. In the head
tag I have preloaded all the heavy banner images in the header and footer, and have done pre-connect and dns-prefetch of all the remaining images. Learn more about preload, prefetch and pre-connect here.
但是这里有一个问题:即使在发送A部分之前,我也向浏览器发送了另一个块,该块是HTML的head
标签。 在head
标签中,我将所有较重的横幅图像预加载到页眉和页脚中,并对所有其余图像进行预连接和dns预取。 在此处了解有关预加载,预取和预连接的更多信息 。
The head
tag also contains the CSS file links. Now as Part A gets prepared on the server, the browser dispatches the request of all the resources in the head
section so that the page gets populated faster when the HTML arrives.
head
标签还包含CSS文件链接。 现在,当在服务器上准备好A部分时,浏览器将在head
部分中分配所有资源的请求,以便在HTML到达时更快地填充页面。
The performance test on both the pages were run with the help of the Lighthouse Extension in Chrome. And the results are really encouraging.
这两个页面上的性能测试都是在Chrome的Lighthouse Extension的帮助下进行的。 结果确实令人鼓舞。
The test was run 10 times on both the pages and the average of all the values is displayed below:
该测试在两个页面上均运行了10次,所有值的平均值显示如下:
Learn more about Time to Interactivity, Speed Index, first meaningful paint.
了解有关交互时间 , 速度指数和第一个有意义的颜料的更多信息 。
This basic idea implementation can drastically improve the performance of a web page. I hope I was able to explain the concept.
这种基本思想的实现可以大大提高网页的性能。 我希望我能够解释这个概念。
If you want to contribute in the idea, or if you feel something better can be done in it, then please go ahead, fork the repo, create a new branch and raise a pull request to me. Please find below the steps to turn on the App on local:
如果您想为这个想法做出贡献,或者您觉得可以做点更好的事情,那么请继续进行,整理仓库,创建一个新的分支,然后向我提出请求请求。 请在下面找到打开本地应用程序的步骤:
Clone from here
从这里克隆
- Install Node on your system 在系统上安装Node
Run
npm install
inside the folder where you have cloned the code在克隆代码的文件夹中运行
npm install
Run
npm run dev
to make thebundle.js
file运行
npm run dev
来制作bundle.js
文件Terminate the process and run
npm start
终止进程并运行
npm start
The App will start running on
8080
port该应用程序将开始在
8080
端口上运行
Please feel free to share this article with your friends if you find it interesting. :)
如果您觉得有趣,请随时与您的朋友分享此文章。 :)
翻译自: https://www.freecodecamp.org/news/web-optimization-a-secret-world-to-unleash-5fbdf1065945/
错过校招