如何使用vite,做vue3.0的服务端渲染(ssr)

新春伊始,想必在座的各位都正在嗷嗷待哺的等待需求中(ps:划水摸鱼),不好意思,理直气壮的说我也是。几天鱼摸下来,心里也不是滋味,看着身边的同学一个个每天都在学这学那,搞得我也不是很好意思。于是趁着现在各种完全体的方案和框架还没出来之前,那我们把vue3.0的服务端搭一搭吧,自己写写还是很有意思的。

好了,废话就先到此,开局调研了基于webpack和vue-cli去搭,途中碰到了一些问题就放弃了,(后记:不好意思,我胡汉三又回来了,怎么说放弃就放弃,反手就是甩一套教程),由于尤大最近很痴迷于vite,vue-cli相关的生态也有些滞后。于是反手就一手vite搞起,看看到底有什么魔力让我们尤大大年三十晚上还在撸代码。

最开始的学习毫无疑问就是看文档,刚好碰上vite2.0发布,简直是可喜可贺。轻车熟路的就找到了ssr的demo
在这里插入图片描述
一看到这句话,就说明接下来的旅途非常的刺激,可以动手发挥想象力的地方非常的多。
话不多说,先download下来,再根据自己想要的去改造就可以了。

打开demo项目,大体的逻辑已经帮我们写的差不多了,剩余服务端预取数据,store状态接管等这些没有去弄,可以说稍加改造就可以用于生产了,非常的nice。

下面讲解下代码吧。
首先看到server.js文件,这个文件其实就是帮我们启动一个ssr的服务器。

// @ts-check
const fs = require('fs')
const path = require('path')
const express = require('express')
const serialize = require('serialize-javascript');

const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD

async function createServer(
  root = process.cwd(),
  isProd = process.env.NODE_ENV === 'production'
) {
  const resolve = (p) => path.resolve(__dirname, p)

  const indexProd = isProd
    ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
    : ''

  const manifest = isProd
    ? // @ts-ignore
      require('./dist/client/ssr-manifest.json')
    : {}

  const app = express()

  /**
   * @type {import('vite').ViteDevServer}
   */
  let vite
  if (!isProd) {
    vite = await require('vite').createServer({
      root,
      logLevel: isTest ? 'error' : 'info',
      server: {
        middlewareMode: true
      }
    })
    app.use(vite.middlewares)
  } else {
    app.use(require('compression')())
    // 把打包好的css,js等文件,放到静态文件服务器
    app.use(
      require('serve-static')(resolve('dist/client'), {
        index: false
      })
    )
  }

  app.use('*', async (req, res) => {
    try {
      const url = req.originalUrl

      let template, render
      // 读取index.html模板文件
      if (!isProd) {
        console.log('当前请求路径', url);
        template = fs.readFileSync(resolve('index.html'), 'utf-8')
        template = await vite.transformIndexHtml(url, template)
        render = (await vite.ssrLoadModule('/src/entry-server.js')).render
      } else {
        template = indexProd
        // @ts-ignore
        render = require('./dist/server/entry-server.js').render
      }
      // 调用服务端渲染方法,将vue组件渲染成dom结构,顺带分析出需要预加载的js,css等文件。
      const [appHtml, preloadLinks, store] = await render(url, manifest)
      // 新加 + 将服务端预取数据的store,插入html模板文件
      const state =  ("<script>window.__INIT_STATE__" + "=" + serialize(store, {isJSON: true}) + "</script>");
      // 把html中的展位符替换成相对应的资源文件
      const html = template
        .replace(`<!--preload-links-->`, preloadLinks)
        .replace(`<!--app-html-->`, appHtml)
        .replace(`<!--app-store-->`, state)

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

  return { app, vite }
}
// 创建node服务器用作ssr
if (!isTest) {
  createServer().then(({ app }) =>
    app.listen(3000, () => {
      console.log('http://localhost:3000')
    })
  )
}

// for test use
exports.createServer = createServer

index.html
就是server.js里面的模板文件,可以看到里面有对应要替换的展位符
在这里插入图片描述
src/main.ts
因为每个请求到达服务端,都需要一份全新的不受上个请求污染的代码,所以这个文件其实就是当前运行环境的工厂函数,每次都返回全新的vue实例,router实例,store实例等。
在这里插入图片描述
src/entry-server.js
服务端渲染入口函数

import { createApp } from "./main";
import { renderToString } from "@vue/server-renderer";

import { getAsyncData } from '@src/utils/publics';

export async function render(url, manifest) {
  const { app, router, store } = createApp();

  // 同步url
  router.push(url);
  store.$setSsrPath(url);
  await router.isReady();
  // 新加 + 当路由准备完毕,调用自定义钩子,在服务端获取数据
  await getAsyncData(router, store, true);

  // 生成html字符串
  const ctx = {};
  const html = await renderToString(app, ctx);

  // 根据打包时生成的服务端预取清单manifest,生成资源预取数组
  const preloadLinks = ctx.modules
    ? renderPreloadLinks(ctx.modules, manifest)
    : [];
  return [html, preloadLinks, store];
}
 省略......

src/entry-client.js
客户端渲染入口函数

import { createApp } from './main'
const { app, router, store } = createApp()

// 这里需要先进行客户端状态同步 - 服务端携带过来的store
// 假设同学们用到的是vuex,我这边用的是自己写的状态管理包,就不写了
// 获取服务端渲染时,注入的__INITIAL_STATE__信息,并同步到客户端的vuex store中
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.isReady().then(() => {
  // 挂在当前vue实例于id为app的dom上
  app.use(VueRescroll)
     .use(VueImageLazyLoad)
     .mount('#app');
})
// 开启路由后置钩子,进行页面数据请求
router.afterEach(() => {
  getAsyncData(router, store, false);
})

getAsyncData
像vue2.0的做法那样,新加asyncData钩子作为数据预取的钩子

// 执行注册store钩子
export const registerModules = (
  components: Component[],
  router: Router,
  store: BaseStore
) => {
  return components
    .filter((i: any) => typeof i.registerModule === "function")
    .forEach((component: any) => {
      component.registerModule({ router: router.currentRoute, store });
    });
};

// 调用当前匹配到的组件里asyncData钩子,预取数据
export const prefetchData = (
  components: Component[],
  router: Router,
  store: BaseStore
) => {
  const asyncDatas: any[] = components.filter(
    (i: any) => typeof i.asyncData === "function"
  );
  return Promise.all(
    asyncDatas.map((i) => {
      return i.asyncData({ router: router.currentRoute.value, store });
    })
  );
};

// ssr自定义钩子
export const getAsyncData = (
  router: Router,
  store: BaseStore,
  isServer: boolean
): Promise<void> => {
  return new Promise(async (resolve) => {
    const { matched, fullPath } = router.currentRoute.value;

    // 当前路由匹配到的组件
    const components: Component[] = matched.map((i) => {
      return i.components.default;
    });
    // 动态注册store
    registerModules(components, router, store);

    if (isServer || store.ssrPath !== fullPath) {
      // 预取数据
      await prefetchData(components, router, store);
      !isServer && store.$setSsrPath("");
    }

    resolve();
  });
};

.vue里面预取数据
跟data,computed同级

	async asyncData({ store, router }: any) {
		if (!store.blog) return;
		const { blogDetail } = store.blog;
        blogDetail.$assignParams({
            id: router.query.id
        })
        await blogDetail.loadData();
	},

对于ssr的改造做了上述这些,还有些项目优化,比如模块化,ts,store的按需注册,以及一些自定义插件等,就不一一道来了,喜欢的同学可以download源码或者fork过去玩玩。
有需要交流的同学,也欢迎评论区交流交流。

项目仓库:https://github.com/Vitaminaq/cfsw-vue-cli3.0/tree/vue3.0-ssr
项目中用到的插件仓库:https://github.com/Vitaminaq/plugins-vue(喜欢的同学可以自取,欢迎同学们加入开发)
有同学想了解vue2.0最开始服务端渲染做法的,可以参考我之前的文章
有想了解vue3.0 + vue-cli服务端渲染的,可参考我最新的文章

2023-05-06补充
在这里插入图片描述
感谢上面的这位同学,让我又活过来了,最新的hooks写法还有点简陋,有时间再补全封装下,参考下这个仓库

好的,我会根据您的要求回答您的问题,以下是从0到1塔建vite+vue3的ssr服务端渲染使用方法,不使用ts: 1. 首先,安装Vite和Vue3 ``` npm install -g vite npm install vue@next ``` 2. 创建项目并初始化 ``` mkdir my-ssr-app cd my-ssr-app npm init -y ``` 3. 安装依赖 ``` npm install vue-server-renderer express ``` 4. 创建服务端入口文件 在项目根目录下创建`server.js`文件,并添加以下内容: ```javascript const express = require('express') const { createRenderer } = require('vue-server-renderer') const { createApp } = require('./src/main') const app = express() // 静态资源目录 app.use(express.static('dist')) const renderer = createRenderer() app.get('*', async (req, res) => { const app = createApp() const html = await renderer.renderToString(app) res.end(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SSR Vite Vue 3</title> </head> <body> <div id="app">${html}</div> <script src="/client.js"></script> </body> </html> `) }) app.listen(3000, () => { console.log('Server started at http://localhost:3000') }) ``` 5. 创建客户端入口文件 在项目根目录下创建`src/main.js`文件,并添加以下内容: ```javascript import { createApp } from 'vue' import App from './App.vue' export function createApp () { const app = createApp(App) return { app } } ``` 6. 配置Vite项目根目录下创建`vite.config.js`文件,并添加以下内容: ```javascript const { createVuePlugin } = require('vite-plugin-vue2') module.exports = { plugins: [ createVuePlugin() ], build: { ssrManifest: true, outDir: 'dist', rollupOptions: { input: 'src/entry-client.js', output: { format: 'esm', entryFileNames: '[name]-[hash].js' } } }, optimizeDeps: { include: [ 'vue', 'vue-router' ] } } ``` 7. 创建组件 在`src`文件夹下创建`App.vue`文件,并添加以下内容: ```vue <template> <div> <h1>{{ message }}</h1> </div> </template> <script> export default { data () { return { message: 'Hello World!' } } } </script> ``` 8. 运行项目 使用以下命令启动项目: ``` vite build && node server.js ``` 9. 查看效果 在浏览器中访问`http://localhost:3000`,即可看到页面渲染结果。 以上就是从0到1塔建vite+vue3的ssr服务端渲染使用方法,不使用ts的步骤。希望对您有所帮助!
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值