# 前言
网络通讯大部分是基于 TCP/IP 的,而 TCP/IP 是基于 IP 地址的,所以计算机在网络上进行通讯时只能识别如“202.96.134.133”之类的 IP 地址,而不能认识域名。
而我们在浏览器输入常见的域名能看到页面,就是有一个 DNS 服务器,将域名翻译成 IP 地址,然后调出 IP 地址所对应的网页。
当我们在浏览器地址栏输入 url 后,就会像前端服务器请求 html 文档,当请求构建是后,就会经过 DNS 域名解析,典型的一次 DNS 解析需要耗费 20-120 毫秒,通常这种情况下的 DNS 解析我们是无法优化的,但是如果在我们项目中外链了其他域名下的文档,在项目加载时,也是需要去解析域名,这一部分是可以做到优化的
# 简单分析
DNS 是有本地缓存的,如果我们解析过当前域名,就会将其缓存在浏览器,下一次遇到这个域名时,就不需要再解析直接返回对应的 IP 了。如果我们在项目文件(js、css、html 等)加载时,异步解析 DNS,就可以在后续遇到其他域名时,直接获取对应的 IP。
dns-prefetch
即 DNS 预获取,DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能 减少用户的等待时间,提升用户体验。
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>SunRain</title>
<meta name="description" content="记录学习笔记" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,user-scalable=no"
/>
<!-- dns预解析 -->
<link rel="dns-prefetch" href="www.baidu.com" />
<link rel="dns-prefetch" href="//g.tbcdn.cn" />
</head>
<body></body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们是能够知道项目中用到了哪些其他域名,那么我们可以在对应的 html 中添加dns-prefetch
,来对这些域名进行预解析。
# 项目构建中实现
虽然我们可以在项目中手动加入要解析的域名,但是我们在开发过程中,很难在每次有新的域名资源添加时想起去添加对应的dns-prefetch
域名,同时在多人协作开发项目时,不同的人可能会有不同的开发习惯,那么这种添加方式就过于简单了。
我们可以用 node 写一个 js 文件,在项目每次打包后去分析打包后的文件,获取项目中遇到的域名,将其 DNS 预解析插入到 index.html 中,这样我们在项目中添加的域名,就会自动生成 DNS 预解析了,所以我们可以在项目中添加一个dns-prefetch.js
来实现这个
const fs = require("fs");
const path = require("path");
const { parse } = require("node-html-parser");
const { glob } = require("glob");
const urlRegex = require("url-regex");
// 获取外部链接的正则表达式
const urlPattern = /(https?:\/\/[^/]*)/i;
// 为了保证域名不重复,所以使用set
const urls = new Set();
// 遍历dist目录中的所有HTML、js、css等文件
async function searchDomain() {
const files = await glob("dist/**/*.(html,css,js)");
for (const file of files) {
// 读取文件内容
const source = fs.readFileSync(file, "utf-8");
// 用urlRegex分析内容中的所有地址
const matches = source.match(urlRegex({ strict: true }));
if (matches) {
matches.forEach((url) => {
// 获取匹配的域名
const match = url.match(urlPattern);
if (match && match[1]) {
// 将域名添加到集合中
url.add(match[1]);
}
});
}
}
}
async function insertLinks() {
// 读取所有html文件
const files = await glob("dist/**/*.html");
// 将之前获取的域名地址转换成link标签
const links = [...urls]
.map((url) => `<link rel="dns-prefetch" href="${url}" />`)
.join("\n");
// 遍历所有html文件
for (const file of files) {
// 读取内容
const html = fs.readFileSync(file, "utf-8");
// 使用parse将内容转译成节点
const root = parse(html);
// 获取head节点
const head = root.querySelector("head");
// 将生成的link标签插入好head中
head.insertAdjacentHTML("afterbegin", links);
// 将内容写回文件
fs.writeFileSync(file, root.toString());
}
}
async function main() {
await searchDomain();
// head中添加DNS预获取标签
await insertLinks();
}
main();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
主要就是步骤如下:
- 读取打包后 dist 目录下的文件,
- 将其中的外链地址利用正则获取,并获取对应的域名地址,
- 用这个域名地址生成对应的 dns 预获取 link 标签
- 读取 dist 目录下的 html 文件,获取
<head>
标签 - 将生成的 link 标签插入到
<head>
标签中,再写入到原来的 html 文件中
# 注意事项
默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取,那么就要使用显示的 DNS Prefetch 了。
Chrome 和 Firefox 3.5+ 内置了 DNS Prefetching 技术并对 DNS 预解析做了相应优化设置。所以即使不设置此属性,Chrome 和 Firefox 3.5+ 也能自动在后台进行预解析。
虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出禁用 DNS 预读取能节省每月 100 亿的 DNS 查询 。
如果需要禁止隐式的 DNS Prefetch,可以使用以下的标签:
<meta http-equiv="x-dns-prefetch-control" content="off" />