惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
Arctic Wolf
T
Tenable Blog
T
Troy Hunt's Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy & Cybersecurity Law Blog
NISL@THU
NISL@THU
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
S
Secure Thoughts
AWS News Blog
AWS News Blog
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
MIT News - Artificial intelligence
T
Tor Project blog
S
Schneier on Security
PCI Perspectives
PCI Perspectives
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Google DeepMind News
Google DeepMind News
V
Visual Studio Blog
爱范儿
爱范儿
Google DeepMind News
Google DeepMind News
Cyberwarzone
Cyberwarzone
T
The Exploit Database - CXSecurity.com
罗磊的独立博客
T
Threat Research - Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
V
V2EX
C
CXSECURITY Database RSS Feed - CXSecurity.com
Stack Overflow Blog
Stack Overflow Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
GRAHAM CLULEY
L
LINUX DO - 热门话题
D
Docker
J
Java Code Geeks
GbyAI
GbyAI
H
Heimdal Security Blog
The Hacker News
The Hacker News
MongoDB | Blog
MongoDB | Blog
V
Vulnerabilities – Threatpost
T
Tailwind CSS Blog
Cloudbric
Cloudbric
TaoSecurity Blog
TaoSecurity Blog
C
CERT Recently Published Vulnerability Notes
Y
Y Combinator Blog
Recorded Future
Recorded Future
Cisco Talos Blog
Cisco Talos Blog
T
Threatpost
The Register - Security
The Register - Security
Hacker News - Newest:
Hacker News - Newest: "LLM"

博客园 - Liaofy

前端新范式:用 AI 提效开发,用 E2E 保证迭代质量 使用mumu模拟器抓包 andriod app 如何在本地给 vue-router4 扩展功能? Vue3 路由优化,使页面初次渲染效率翻倍 【eslint 插件开发】禁用 location 跳转外部链接 vue 项目使用 charles 代理线上页面到本地后显示404 避免使用单个数字作为对象的key 高扩展弹出层组件设计实现(H5&小程序) 轻松搞懂POST与PUT的区别 vite(or rollup) 实现 webpackChunkName webapp项目架构畅想(vue3).md vite试玩:老项目启动从6分钟到秒开 修剪AST树减少webapck打包尺寸 【uniapp】改善中大型uniapp小程序项目开发体验 vscode 调试 webpack 打包 5分钟,搞清 Unicode 与 UTF-8 的区别 deAsync.js 真正让异步变同步 盘的转——使用缓动函数完成动画 多个请求间断失败,如何优雅处理?
把SPA的加载速度提升一倍,有没有搞头?
Liaofy · 2024-04-29 · via 博客园 - Liaofy

众所周知,纯前端渲染的单页应用(SPA)渲染比较慢,其中一个问题是:先加载公共js,再根据路由动态加载页面需要的js。如下图:

  1. commons.e7c0f857.js加载并解析完毕后(2.53s)
  2. 再发起对order.15cd187e.js的加载(2.24s)。

本文以 VUE 项目为例。

为什么会出现串行加载的情况

诚然,这是由于路由懒加载造成的。但import()说这个锅它不想背,因为动态加载,本来就是在需要的时候才去加载。

把串行加载改造成并行加载

想要加速js加载过程,必须将上述js进行并行加载,这看起来和动态加载十分矛盾。

不过,我们暂且不要考虑那么多,先看看能不能做。

一个常见的路由配置如下:

{
 path: '/domestic/detail',
 component: () =>
   import(
     /* webpackChunkName: "jipiao/search/domestic" */ '../domestic/detail'
   ),
}

要实现文件的并行加载,我们只需要两步操作:

  1. 找到path需要的全部js/css文件,得到一个配置文件
  2. 当请求到达服务器时,将匹配到的文件写入到html后,再返回给浏览器

生成 path 对应的配置文件

如果你用的是webpack进行打包,很容易通过自定义插件,在emit hook得到path对应的js/css依赖文件,最终生成一个配置文件。以下代码仅作参考:


class PreloadChunkPlugin {
  apply(compiler) {
    const webpackChunkNameMap = pathList.reduce((acc, item) => {
      acc[item.webpackChunkName] = {
        path: item.path,
        chunks: []
      }
      return acc
    }, {})
    
    const mode = compiler.options.mode;
    compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
      const chunkDepSet = compilation.chunks.reduce((acc, chunk) => {
        const chunkName = chunk.name
        if (chunkName && webpackChunkNameMap[chunkName]) {
          if (!acc[chunkName]) acc[chunkName] = {
            chunkSet: new Set(),
            chunkIdSet: new Set()
          }
          ;[...chunk._groups].forEach(chunkGroup => {
            chunkGroup.chunks.forEach(subChunk => {
              const hash = mode === 'production' ? subChunk.contentHash.javascript.substring(0, 8) : ''
              acc[chunkName].chunkSet.add(`${(subChunk.name || subChunk.id)}${hash ? '.' + hash : ''}.js`)
              acc[chunkName].chunkIdSet.add(subChunk.id)
            })
          })
        }
        return acc
      }, {})

      const pathMap = {}
      Object.keys(chunkDepSet).forEach(key => {
        const {
          chunkSet,
          chunkIdSet,
        } = chunkDepSet[key]
        pathMap[webpackChunkNameMap[key].path] = {
          chunks: [...chunkSet],
          chunkIds: [...chunkIdSet]
        }
      })

      fs.writeFileSync(
        path.resolve(__dirname, '../../app/assets-path2chunks.json'),
        JSON.stringify(pathMap)
      );

      callback();
    });
}

当然,vite插件也可以在generateBundle中做到。

好了,假设我们已经得到了最终的配置文件:

{
  '/domestic/detail': ['detail.js', 'other.js']
}

动态写入到 html 文件中

我们的项目恰好使用node服务来返回 html,所以比较容易实现,根据请求的路径来匹配对应的依赖文件,然后写入到html中,再返回给服务器。

如果你使用的是nginx,也可以考虑编写lua脚本,实现动态写入的过程。

下图是改造后的效果:

FAQ

Q: 为什么不用多页?

A: 多页应用之间跳转比较慢,应用之间通信也相对麻烦。改造对业务也有侵入。

Q: 为什么不用服务端渲染(ssr)

A: ssr 效果当然更好。但我们项目很大,改造成本过高。新项目当然推荐!