文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

使用vite搭建ssr活动页架构的实现

2024-04-02 19:55

关注

前言

最近接了个需求,重构公司的活动页项目。要实现:

基于这些需求,我选择了 vite + react + vite-plugin-ssr

文章前面是ssr入门,老手请随意跳过,看最后即可

入门SSR

什么是SSR

术语

ssr的历史

我的学习习惯是,不论学什么,先去了解它的历史背景。存在即合理,了解到为什么产生一个技术,能让我更容易去理解这门技术

最初的网页渲染,前端三剑客:html + css + js,放在服务器上,静态部署就可以供用户访问了。

后来随着网页复杂度上升,出现了jsp/ejs等等一系列模板语法,在服务端获取到数据后,把数据渲染到模板中,最后生成html返回给客户端,这是最原始的ssr。

随着前端框架的诞生(ng/react/vue),越来越多同学开始使用框架开发web,这些前端框架的出现使得前后端开发解耦(csr的情况下),前端同学可以更充分的利用前端工程化等等新技术来健壮前端项目。而这种完全解耦的方式也带来了一些问题,比如非常不友好的SEO

csr的缺点

让我们打开一个SPA网页(使用脚手架默认方式搭建),右键查看网页源代码

第一个问题:SEO极度不友好。 网页里面根本没有内容。爬虫最喜欢这种网页了,看一眼就走。

SPA的工作方式就是使用js来动态渲染html,压力全部给到了客户端(浏览器)这边,正是因为这个,第二个问题也出现了:首屏的加载速度较慢

为什么ssr的需求再次出现

为了更好的SEO,为了更快的加载速度(服务端生成了首页静态页面,客户端可以直接展示,随后再用JS动态渲染)

前端开发使用react/vue,可以熟练开发网页。而cra/vue-cli脚手架创建出来的模板默认是SPA。

那么应该如何实现 “既要,还要”呢(前端框架/seo我全都要)

如何实现基础ssr

基于上面的问题,我们希望实现:

既然需要服务端渲染,服务端用来执行vue/react这种js框架,那第一反应就是用nodejs来做服务端渲染,因为nodejs天然执行js代码

客户端的话,用vue来做(react也行,只不过最近在熟悉vue3),vue3的话,体积比react更小,toC网站更好一些。react18针对ssr出了新api,开发者可以使用 React.lazysuspense 实现懒加载,也提供了很好的用户体验:https://github.com/reactwg/react-18/discussions/37

下面是基础的ssr例子

以下例子 请注意:客户端使用的是esm规范,服务端使用的是cjs

如果希望统一使用esm,可以使用 tsx 执行node脚本 或修改package.json => type: "module"

创建服务端

const express = require('express')

const app = express()

app.get('*', (req, res) => {
  res.send('Hello World')
})

app.listen(4000, () => {
  console.log('Server running at http://localhost:4000');
})

启动服务后,打开浏览器 http:localhost:4000,即可看到内容

渲染vue

服务端有了,但是是返回的string,我们想用vue来开发,尝试返回一个vue组件

vue3提供了服务端渲染组件的方法,在 vue/server-renderer

const express = require('express')
const { renderToString } = require('vue/server-renderer')
const { createSSRApp } = require('vue')

const app = express()

app.get('*', (req, res) => {
  const vue = createSSRApp({
    data: () => ({ count: 1 }),
    template: `<button @click="count++">{{ count }}</button>`,
  })

  renderToString(vue).then((html) => {
    res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="app">${html}</div>
    </body>
    </html>
    `)
  })
})

app.listen(4000, () => {
  console.log('Server running at http://localhost:4000')
})

此时打开页面,可以看到button了,但是此时页面是静态的,因为这个页面在服务端已经渲染好了,但在客户端没有注入vue

右键查看网页源代码,可以看到button元素

客户端渲染

我们希望button的交互可以动起来,此时需要客户端来做渲染了

const { createSSRApp } = require('vue')

const vue = createSSRApp({
  data: () => ({ count: 1 }),
  template: `<button @click="count++">{{ count }}</button>`,
})

vue.mount('#app')

这段代码是否很眼熟,其实基本上跟服务端渲染返回的内容是一样的。所以ssr的本质是服务端渲染静态html+客户端渲染js

此外,为了在浏览器中加载客户端文件,我们还需要:

const express = require('express')
const { renderToString } = require('vue/server-renderer')
const { createSSRApp } = require('vue')

const app = express()

app.get('/', (req, res) => {
  const vue = createSSRApp({
    data: () => ({ count: 1 }),
    template: `<button @click="count++">{{ count }}</button>`,
  })

  renderToString(vue).then((html) => {
    res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script type="importmap">
      {
        "imports": {
          "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
        }
      }
      </script>
      <script src="/client.js" type="module"></script>
    </head>
    <body>
      <div id="app">${html}</div>
    </body>
    </html>
    `)
  })
})

app.use(express.static('.'))

app.listen(4000, () => {
  console.log('Server running at http://localhost:4000')
})

此时打开本地地址,可以看到点击button数字变化了

以上是最简单的ssr,在vue官网上可以找到这个例子。

我们甚至没有去考虑前端的路由,状态管理 等等。一个完整的ssr还需要一系列构建。

网页路由

ssr的网页路由有两种方式

服务端路由

服务端路由,就是利用 web框架的路由能力,匹配到某个路由时,返回对应的html代码,并且加载相应的客户端代码,比如:

import express from 'express'

const router = express.Router()

router.get('/some-page', (req, res) => {
  // 返回 some-page 的html
  res.send(`<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script src="/client.js" type="module"></script>
    </head>
    <body>
      <div id="app">要渲染的html字符串</div>
    </body>
    </html>`)
})

服务端路由跳转直接使用 a标签即可

客户端路由

客户端路由的话,就要用到前端框架对应的路由库,vue-router / react-router 等

可以参照官方例子做

比较两种方式

服务端路由适合做页面零碎的项目,如活动页,每次跳转路由会刷新整个页面

客户端路由适合做页面之间交互强的项目,如产品页,跳转路由不会刷新页面

使用vite做ssr

vue官方推荐了几个做ssr的例子,包括 Nuxt/ Quasar这种重框架,也有 vite的轻框架。为了细粒度把控项目,我使用了 vite+ vite-plugin-ssr的方案来做

vite-plugin-ssr

Like Next.js / Nuxt but as do-one-thing-do-it-well Vite plugin.

类似 Next/Nuxt 但是只做一件事并把它做好 的vite插件

这个插件的文档写得非常详细,而且github上有许多例子。

插件的具体功能我不赘述,各位可看官方文档,我在这里讲一下这个插件(v0.3x)的约定式路由的工作原理。以下 vite-plugin-ssr 简称为 vps

vps的约定式路由

vps推荐使用文件夹名称作为路由,这种方式也是最方便的。活动页不存在页面之间的交互,所以我选择的默认方式。

vps规定了一系列文件命名,作为开发/构建遍历的条件。以下4种命名会被vps收集,每种文件有其独特的作用。我们不要随意以 page.*** 来命名文件

// Vite resolves globs with micromatch: https://github.com/micromatch/micromatch
// Pattern `*([a-zA-Z0-9])` is an Extglob: https://github.com/micromatch/micromatch#extglobs
export const pageFiles = {
  //@ts-ignore
  '.page': import.meta.glob('*.page.*([a-zA-Z0-9])'),
  //@ts-ignore
  '.page.client': import.meta.glob('*.page.client.*([a-zA-Z0-9])'),
  //@ts-ignore
  '.page.server': import.meta.glob('*.page.server.*([a-zA-Z0-9])'),
  //@ts-ignore
  '.page.route': import.meta.glob('*.page.route.*([a-zA-Z0-9])'),
}

dev阶段

build阶段

项目大了之后,打包速度慢该怎么办?

做活动页,每个页面之间是没有关联的,其实我希望打包是增量式的打包,但是如果公共文件改变了,也无法避免全量打包。所以如果能做到缓存打包文件,就可以提升打包速度。

理想美好,现实往往相反。rollup2并不支持content hash,但是好消息是rollup3支持了并且会在最近发布

目前我们只能用hack的方式去实现content hash,比如使用node的 crypto模块来做md5hash

import { createHash } from 'crypto'
import type { PreRenderedChunk } from 'rollup'

export function getContentHash(chunk: string | Uint8Array) {
  return createHash('md5').update(chunk).digest('hex').substring(0, 6)
}

export function getHash(chunkInfo: PreRenderedChunk) {
  return getContentHash(
    Object.values(chunkInfo.modules)
      .map((m) => m.code)
      .join(),
  )
}

然后在rollup的output中设置文件的命名

rollupOptions: {
  treeshake: 'smallest',
  output: {
    format: 'es',
    assetFileNames: (assetInfo) => {
      let extType = path.extname(assetInfo.name || '').split('.')[1]
      if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType!)) {
          extType = 'img'
          }
      const hash = getContentHash(assetInfo.source)
      return `assets/${extType}/[name].${hash}.[ext]`
    },
    
    chunkFileNames: (chunkInfo) => {
      const server = chunkInfo.name.endsWith('server') ? 'server-' : ''
      const name = chunkInfo.facadeModuleId?.match(/src/pages/(.*?)//)?.[1] || chunkInfo.name
      
      if (chunkInfo.isDynamicEntry || chunkInfo.name === 'vendor') {
        const hash = getHash(chunkInfo)
        return `assets/js/${name}-${server}${hash}.chunk.js`
      } else {
        return `assets/js/${name}-${server}[hash].chunk.js`
      }
    },
    entryFileNames: (chunkInfo) => {
      if (chunkInfo.name === 'pageFiles') {
        return '[name].js'
      }
      const hash = getHash(chunkInfo)
      return `assets/js/entry-${hash}.js`
    },
  },
},

做了content-hash后,打包速度会有非常大的提升,因为rollup其实有个cache机制,针对cache的文件不会transform,而正好transform是非常耗时的一步。

我尝试了打包1000个文件,耗时40+s,在我的接受范围内

快速创建页面模板

活动页面会有比较多相似的地方,所以直接根据模板来创建页面代码,开发效率又高一点(又可以摸鱼了)。代码地址

做得不好的地方

记录两个ssr探索过程中,我想实现,但最后没有实现的

部署

部署的话,打算使用docker来做,下篇文章再讲

源码地址

react + ssr

vue3 + ssr

到此这篇关于使用vite搭建ssr活动页架构的实现的文章就介绍到这了,更多相关vite搭建ssr活动页内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯