vue3 vitessr源码解析

vuessr关键流程

vue3 vitessr源码解析

流程有了我们再来看下一些过程中的点

vite启动

在 dev 下,我们需要 SSR 提供和非 SSR 模式一致的极速的 HMR 体验。

下面例子是一个启动 vite SSR dev Node.js 端的用例。主要用到了 ssrLoadModule SSR 运行的 API。

import express from 'express'
import { createServer } from 'vite'
const app = express()
const vite = createServer({
  root,
  server: {
    middlewareMode: true,
    watch: {
      // During tests we edit the files too fast and sometimes chokidar
      // misses change events, so enforce polling for consistency
      usePolling: true,
      interval: 100
    },
    hmr: {
      port: hmrPort
    }
  },
  appType: 'custom'
})
app.use(vite.middlewares)
app.use('*', async (req, res) => {
  try {
    let template = fs.readFileSync(resolve('index.html'), 'utf-8')
    template = await vite.transformIndexHtml(url, template)
    const render = (await vite.ssrLoadModule('/src/entry-server.js')).render

    const [appHtml, preloadLinks] = await render(url, manifest)

    const html = template
      .replace(`<!--preload-links-->`, preloadLinks)
      .replace(`<!--app-html-->`, appHtml)

    res
      .status(200)
      .set({ 'Content-Type': 'text/html' })
      .end(html)
  } catch (e) {
    res.status(500).end(e.stack)
  }
})

ssrLoadModule

这个 API 的作用是在 Node.js 环境下加载模块及其依赖,并且在 Vite 同一个 Node.js 环境下执行,并且让这个模块经过 Vite 所有插件 SSR 模式转换,让同一份代码在 SSR 模式下也支持 Vite 提供的语法糖。

下面介绍下这个方法的 happy path。

  • ensureEntryFromUrl,解析加载的 URL,并在模块图上创建这个模块。
  • ssrTransform,将所有的 esm import, export 都转换成 vite_ssr_* 函数。
  • 执行模块。使用 AsyncFunction 提供 vite_ssr_* 相关函数,对原来的 import 语句将会执行ssrLoadModule继续加载,对 export 语句则在 ssrModule 对象上添加返回对象的引用。

❓ 循环依赖问题

  • 因为模块对象在加载之前就已经注册到模块图上了,如果这个模块也正在初始化就会直接返回这个模块ssrModule引用。
  • 如果加载中的模块还是被再次调用ssrLoadModule去加载,也是直接从模块图上取这个模块ssrModule引用,避免模块二次加载。

vite 在加载模块的时候,避免模块进行二次加载,循环依赖获取到的是这个模块没有初始化完成的ssrModule引用。

❓ 为什么快

SSR dev 处理逻辑和纯 dev 处理逻辑是一致的。SSR 还是根据运行时需要加载的模块进行实时编译然后放到 Node.js 环境下执行,纯 dev 环境根据浏览器加载模块进行请求 vite 然后实时编译返回到浏览器执行。他们都还是保持着 vite 细颗粒度的更新和编译模块,所以在 hmr 的场景下还是很快。

vue ssr虚拟 dom相关

vue-server-renderer 包提供了 createBundleRenderer 的 api,可以传入编译打包后的 bundle 代码来创建一个 renderer。renderer 有 renderToString 和 renderToStream 的 api。

内部会通过 vue.createVNode 来执行 bundle 的代码,产生 Vue 实例,之后把 Vue 实例的 vdom 渲染成 html 字符串。返回这个 html 字符串就实现了 SSR

vue3 vitessr源码解析

vue3 vitessr源码解析

async function renderToString(input, context = {}) {
    // 输入的内容是一个虚拟节点(vnode),则会将其包装在一个带有上下文的应用程序中,并再次调用renderToString函数进行处理
    if (isVNode(input)) {
        // raw vnode, wrap with app (for context)
        return renderToString(vue.createApp({ render: () => input }), context);
    }
    // rendering an app
    // 输入的内容是一个应用程序(app),则会创建一个虚拟节点,并将输入的组件和属性赋值给该虚拟节点。然后,将上下文信息提供给组件树中的所有组件,以便在渲染过程中使用。
    const vnode = vue.createVNode(input._component, input._props);
    vnode.appContext = input._context;
    // provide the ssr context to the tree
    input.provide(vue.ssrContextKey, context);
    // 函数会调用renderComponentVNode函数渲染该虚拟节点,并将结果作为一个缓冲区(buffer)返回。这个缓冲区需要经过unrollBuffer函数的处理,最终得到结果
    const buffer = await renderComponentVNode(vnode);
    const result = await unrollBuffer(buffer);
    // 函数会调用resolveTeleports函数解析组件中的传送门(teleports),并返回最终的结果。
    await resolveTeleports(context);
    return result;
}

SSR的运作过程V3

编译(集中在vite)

"build:client": "vite build --ssrManifest --outDir dist/client",

在构建客户端代码的时候需要额外生成ssrManifestssrManifest的作用是告知 Node.js 端渲染这个模块下所有的依赖。相关代码

"build:server": "vite build --ssr src/entry-server --outDir dist/server",

Vite 需要对 SSR 应用需要使用同一份代码构建在 Node.js 和浏览器运行的代码。以 Vue.js 插件为例,当 Vue 编译器接受到ssr: true参数后,就会将模版生成的代码从生成浏览器运行代码 转换成 拼接字符串。这个时候主要做的是代码聚合以及esmodule到cjs转换

初始化

@vue/server-renderer 中的 renderToString 方法通过调用 createRenderContext 函数创建渲染上下文,并将渲染上下文和渲染函数(通过 createApp().component.render 生成)传递给 ssrRender 函数进行渲染。

渲染

ssrRenderComponentssrRenderListssrRenderSuspense等。这些函数用于在服务器端渲染不同类型的组件和指令

ssrGetDirectivePropsssrGetDynamicModelProps。这些函数用于在服务器端渲染过程中获取指令的属性

原文链接:https://juejin.cn/post/7265867535006203916 作者:sean聊前端

(0)
上一篇 2023年8月12日 上午11:07
下一篇 2023年8月13日 上午10:00

相关推荐

发表回复

登录后才能评论