最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 玩起来,使用vite,做vue3.0的服务端渲染(ssr)

    正文概述 掘金(Vitaminaq)   2021-03-01   1964

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

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

    最开始的学习毫无疑问就是看文档,刚好碰上vite2.0发布,简直是可喜可贺。轻车熟路的就找到了ssr的demo。 玩起来,使用vite,做vue3.0的服务端渲染(ssr) 一看到这句话,就说明接下来的旅途非常的刺激,可以动手发挥想象力的地方非常的多。 话不多说,先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里面的模板文件,可以看到里面有对应要替换的展位符 玩起来,使用vite,做vue3.0的服务端渲染(ssr)
    src/main.ts
    因为每个请求到达服务端,都需要一份全新的不受上个请求污染的代码,所以这个文件其实就是当前运行环境的工厂函数,每次都返回全新的vue实例,router实例,store实例等。 玩起来,使用vite,做vue3.0的服务端渲染(ssr)
    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()
    
    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过去玩玩,经实验ssr暂时不支持tsx/jsx写法。 有需要交流的同学,也欢迎评论区交流交流。

    项目仓库:github.com/Vitaminaq/c… 项目中用到的插件仓库:github.com/Vitaminaq/p…(喜欢的同学可以自取,欢迎同学们加入开发)


    起源地下载网 » 玩起来,使用vite,做vue3.0的服务端渲染(ssr)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元