您好, 假如喜欢我的文章或许想上岸大厂,能够重视大众号「量子前端」,将不定时重视推送前端好文、共享工作材料秘籍,也期望有时机一对一帮助你完成愿望

简略来说,DNS 的作用是将域名解析为 IP 地址,解析的进程是耗时的,转化后会做本地缓存,咱们的优化的方针主要是针对用户第一次拜访站点的时分堕入长时刻白屏的问题。

DNS 解析能够分为两类,第一类是页面 DNS 解析,当用户输入 url 地址后(比方是第一次拜访站点),便开端页面 DNS 解析,这个进程是省不了的,因为你无法在用户拜访站点之前就让他提早把 DNS 解析好;

第二类是其他资源的 DNS 解析,在浏览器解析 html 的时分,会遇到一些 script 元素、link 元素,此时会暂停 html 的解析,转而加载 JS,里面就包含了 DNS 解析,这个进程是耗时的,会阻塞浏览器渲染主线程,所以该怎么进行优化呢?答案是采用 DNS 预解析!

一、DNS预解析是什么

DNS预解析dns-prefetch )是前端网络功能优化的一种办法,它根据浏览器定义的规则,提早解析之后可能会用到的域名,使解析成果缓存到体系缓存中,缩短DNS解析时刻,从而进步网站的拜访速度。

DNS预解析能够让浏览器在用户拜访链接之前解析域名,其范围包括文档的一切链接,包括图片、CSS、JS;域名解析后,假如用户确实拜访该域名,那么DNS解析时刻将不会有推迟。因为预读取会在后台执行,所以DNS很可能在链接对应的东西呈现之前就现已解析完毕,这能够削减用户点击链接时的推迟。

二、DNS预解析的原理

当浏览器拜访一个域名的时分,需求解析一次 DNS,获得对应域名的 ip 地址;在解析进程中,依照如下的顺序逐步读取缓存,直到拿到IP地址:

  • 浏览器缓存
  • 体系缓存
  • 路由器缓存
  • ISP(运营商)DNS缓存
  • 根域名服务器
  • 顶级域名服务器
  • 主域名服务器

dns-prefetch 就是在将解析后的IP缓存在体系中;这样就有效地缩短了 DNS 解析时刻。因为在本地操作体系做了 DNS 缓存,使得 DNS 在解析的进程中,提早在体系缓存中找到了对应 IP;这样一来,后续的解析进程就不用执行了,从而也就缩短了 DNS 解析时刻。

假设浏览器初次将一个域名解析为 IP 地址,并缓存至操作体系,那么下一次 DNS 解析时刻能够低至 0-1ms;假使成果不缓存在体系,那么就需求读取路由器的缓存,从而后续的解析时刻最小也要约 15ms

假如路由器缓存也不存在,则需求读取 ISP(运营商)DNS缓存,一般像 taobao.combaidu.com 这些常见的域名,读取ISP(运营商)DNS 缓存需求的时刻在 80-120ms,假如是不常见的域名,均匀需求 200-300ms

那也就是说,dns-prefetch 能够给 DNS 解析进程带来 15-300ms 的提高,尤其是一些许多引证许多其他域名资源的网站,提高作用就更加明显了。

三、怎么敞开DNS预解析

在 HTML 的 head 部分增加以下代码来启用 DNS 预解析,href 特点指定了需求预解析的主机名:

<link rel="dns-prefetch" href="//baidu.com">

需求注意的是,dns-prefetch 仅对跨域域上的 DNS 查找有效,因此请避免运用它来指向相同域

HTTP 页面下一切的 a 标签的 href 都会自动去启用 DNS Prefetch,也就是说,你网页的 a 标签 href 带的域名,是不需求在 head 里面加上 link 手动设置的。

四、敞开DNS预解析的前后对比

准备了一个例子,运用了 logo 的地址,预解析该图片所在域名 dns,然后在点击切换按钮的时分把当时图片替换成 logo

一对HTML标签,让你的网站白屏时刻削减300ms!

点击切换按钮,检查 network 该图片恳求的 Timing,发现少了一个 DNS 解析阶段:

一对HTML标签,让你的网站白屏时刻削减300ms!

再来看看不敞开预解析 dns 的状况:

一对HTML标签,让你的网站白屏时刻削减300ms!

成果如下,多了 dns 解析这一阶段:

一对HTML标签,让你的网站白屏时刻削减300ms!

继续点击切换,恳求如下,dns 解析、树立连接时刻(Socket) + SSL认证时刻都没有:

一对HTML标签,让你的网站白屏时刻削减300ms!

其实就等于 preconnect(dns 解析、树立连接时刻(Socket) + SSL认证时刻都提早):

  <link rel="preconnect" href="https://lf-cdn-tos.bytescm.com/">

五、项目中运用DNS预解析

在项目中咱们可能会遇到一个问题,就是许多当地运用到了第三方的外链,比方图片、CSS、JS,因为项目是团队开发,有时分还不知道项目哪些当地引证了第三方的外链,所以咱们不可能通过 link 标签的方式将这些第三方外链一个个引入并敞开 DNS 预解析。

这个时分需求写一个插件去帮咱们查找项目中一切的引入的第三方外链,在网上搜了下找不到满意需求的插件,只能自己写了,因为 Vite 项目运用 rollup 打包的,而 Vue-cli 项目用的是 webpack,不同的东西打包机制是不一样的,所以不采用插件的方式,在运行打包命令的时分运行一段代码,去修改打包的成果,这里以 Vite 工程为例:

// package.json
"scripts": {
    "build": "vite bulid && node ./scripts/dns-prefetch.js"
}

具体的代码如下,简略来说就是,遍历打包后的 dist 目录中的一切 HTML、JS、CSS 文件,将一切外链的域名存起来,然后在 dist 目录下 index.html 文件的 head 标签中依次刺进 link 标签,一起敞开 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
const urls = new Set()
// 遍历dist目录中的一切HTML、JS、CSS文件
async function searchDomin() {
    const files = await glob('dist/**/*.{html,css,js}')
    for (const file of files) {
        const source = fs.readFileSync(file, 'utf-8')
        const matches = source.match(urlRegex({ strict: true }))
        if (matches) {
            matches.forEach((url) => {
                const match = url.match(urlPattern)
                if (match && match[1]) {
                    urls.add(match[1])
                }
            })
        }
    }
}
// 在index.html文件<head>标签中刺进link标签
async function insertLinks() {
    const files = await glob('dist/**/*.html')
    const links = [...urls].map((url) => `<link rel="dns-prefetch" href="https://juejin.im/post/7336812458646290495/${url}" />`).join('n')
    for (const file of files) {
        const html = fs.readFileSync(file, 'utf-8')
        const root = parse(html)
        const head = root.querySelector('head')
        head.insertAdjacentHTML('afterbegin', links)
        fs.writeFileSync(file, root.toString())
    }
}
async function main() {
    await searchDomin()
    await insertLinks()
}
main()

假如喜欢我的文章或许想上岸大厂,能够重视大众号「量子前端」,将不定时重视推送前端好文、共享工作材料秘籍,也期望有时机一对一帮助你完成愿望。