记一次vue3项目转nuxtjs项目实践 - Shiina's Blog

记一次vue3项目转nuxtjs项目实践

2022 年 9 月 27 日 - 19:49:12 发布
6.8K 字 23分钟
本作品采用 CC BY-NC-SA 4.0 进行许可。
本文距离首次发布已经过去了 591 天,请注意文章的时效性!

记一次vue3项目转nuxtjs项目实践

项目背景

之前接了一个外包游戏网站,项目是计划用 vue3 + vite 进行开发,整体网站框架写完后突然说增加一个甲方,是一个游戏社团,然后也是套用这个网站的模板,对方提出可能需要优化搜索引擎对网页的抓取,所以我想到了使用服务端渲染对网站进行处理的方法。

什么是服务端渲染(SSR)

先说几个本文提到的概念

  • SSR (server side rendering)服务端渲染,是指由整个 HTML 页面在服务器完成页面的DOM结构拼接,然后发送到浏览器,为其绑定状态与事件,成为完全可交互页面的过程。
  • SEO(search engine optimization)搜索引擎优化,利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。
  • SPA(single page application)单页面应用,这种网页不会因为任何操作或导航切换而重新刷新页面,只是局部更新内容。
  • AJAX(Asynchronous Javascript And XML)异步JS & XML,前端发送 AJAX 请求后不阻塞其他操作,数据接手后再异步更新页面,即可以在不重新加载整个网页的情况下,对网页的局部数据进行更新。

在早期的网页开发中,许多网页的逻辑都比较简单,没有特别复杂的需求

随着前端技术不断地发展,页面需求逐渐变得多种多样,用户对网页的体验要求也变得越来越高,而 AJAX 的出现把前端的开发带入了一个新的模式:前后端分离,即前端可以专注于对页面的视觉处理与交互,不依赖于后端;后端则可以专注于编写接口,为前端提供服务。前端拿到后端接口的数据后再生成 HTML 并在用户的浏览器上渲染出来,这个就是 客户端渲染,即借用客户端的硬件进行页面渲染,由此各种前端框架应运而生:Vue, React, Angular, Svelte, Solid JS,同时配合着 Node.js 以及各种网页打包工具如:Webpack, gulp,以及现在比较热门的 Vite 使得前端的开发效率获得了大大的提高。

但是使用框架写出来的这种 SPA 应用也有一些缺点,比如:

  • 对 SEO 不友好,因为 HTML 源码是只引入了 js 的一个文件,比如:
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <link rel="icon" type="image/svg+xml" href="/vite.svg" />
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>USS Jackdaw Server</title>
      <script type="module" crossorigin src="/assets/index.f4dd723c.js"></script>
      <link rel="stylesheet" href="/assets/index.04983a65.css">
    </head>
    
    <body>
      <div id="app"></div>
    </body>
    
    </html>
    而搜索引擎是通过对网页的文本以及关键字进行抓取的,我们通过 js 生成的结构并不会被抓取到。
  • 首屏加载速度慢,因为页面内容是由js拼接出来的,所以浏览器会等待js处理完成后才能渲染真正的DOM结构。

为了解决这些问题,人们便提出了服务端渲染这种解决方案,目的是优化首屏加载时间与 SEO 效果。

目前两款前端主流框架都有他们自己官方推出的服务端渲染插件,React 官方推出的是 Next.js,而 Vue 官方推出的是 Nuxt.js

本次项目使用的是 Vue3,因此在此处选择使用 Nuxt.js 进行处理。

项目遇到的困难

说实话挺多的,因为用的是 Vue3,所以 Nuxt 部分也得选用 Nuxt3,因为 Nuxt2 不能往高版本使用

Nuxt3英文文档官网:https://v3.nuxtjs.org/

国内热心人士翻译的部分文档:https://57code.gitee.io/nuxt3-docs-zh/

Nuxt3 有一个优点是可以自动引入包,而且是根据你使用的插件进行自动判断引入,这样对于常用的插件就减少了手动引入的次数,提升了开发效率

Nuxt3 的使用与现存的许多插件也都有一些出入与不符,本人就在这里总结一下安装各种包的经验与配置的技巧。

Npm 包引入

Pinia

对于大部分插件,你都需要到他们官网下的 服务端渲染 来查看正确的引入方式,比如 Pinia 的文档如下:

这个还算是比较简单的,官网只需要在配置文件下加上这些即可:

// nuxt.config.ts
export default {
  // ... other options
  buildModules: [
    // Nuxt 2 only:
    // https://composition-api.nuxtjs.org/getting-started/setup#quick-start
    '@nuxtjs/composition-api/module',
    '@pinia/nuxt',
  ],
}

但这是 Nuxt 2 only,而本人实测安装的时候是需要去掉第一行的 module 即可正常使用,原因也不太清楚

Swiper

项目中使用到了 Swiper,所以同样去官网找安装方法,但翻了一圈都没有找到,甚至官网都没有,后面决定先放着,找一下其他插件的引入方法。

项目中原来还有一个瀑布流页面的插件要使用,但是后来弃用了,不过很幸运的是他的官网有使用 SSR 的配置。

原文:https://github.com/shershen08/vue-masonry#nuxt-ssr-implementation

其中提到了一个插件 (plugins) 概念,这玩意我在官网有印象:https://v3.nuxtjs.org/guide/directory-structure/plugins

学着这个插件提供的指南,我写出了如下的引入方式:

// plugins/swiper.ts

import Swiper from 'vue-awesome-swiper'
import SwiperClass, { Navigation, Pagination } from 'swiper'

export default defineNuxtPlugin(nuxtApp => {
  // Doing something with nuxtApp
  SwiperClass.use([Navigation, Pagination])
  nuxtApp.vueApp.use(Swiper)
})
// nuxt.config.ts
export default defineNuxtConfig({
  // ...
  plugins: [
    { src: '@/plugins/swiper', mode: 'client' },
  ],
  build: {
    transpile: ['swiper'],
  },
  css: [
    "swiper/css/bundle"
  ]
});

随后在 vue 组件中直接写就可以了,不需要在组件内再次引入,因为 Nuxt3 自带自动引入组件功能。

有一个问题就是如果直接进入有轮播图的页面中,Vue 会在控制台发出警告:

大概的意思是服务端渲染的 Dom 与 客户端的节点不匹配,这个的原因我猜想是因为我设置了插件是 只在客户端进行渲染 的原因,本人去 GitHub Issue 上也找了一圈,但是并没有理想答案,所以猜想是轮播图插件的兼容性并不是很好,导致会有这种警告。不过并不影响正常使用,所以我也没有继续探究下去了。

I18n 国际化

这个比较简单,直接在 plugins 文件夹下新建插件就行了

// i18n.ts

import { createI18n } from 'vue-i18n'

export const I18N = createI18n({
  // your options
})

export default defineNuxtPlugin(nuxtApp => {
  // Doing something with nuxtApp
  nuxtApp.vueApp.use(I18N)
})

路由动画切换

这个还好,在 Nuxt3 中可以在 config 对全局变化进行配置,也可以对单独某个页面进行配置。

文档:https://v3.nuxtjs.org/api/configuration/nuxt-config#pagetransition

配置方式与 vue3 官网文档区别不大,我就在这里放一下自己是怎么写的:

// nuxt.config.ts
export default {
  // ... other options
  pageTransition: {
    name: "page",
    mode: "in-out",
    duration: 1000
  },
}

如果需要对某个页面单独使用一种效果则在那个页面组件这么写:

<script setup>
definePageMeta({
  pageTransition: {
    // 在此处设置你的 Transition
  },
});
</script>

顶层 await

项目在最后尝试打包的时候报了这样的一个错误:

错误似乎是与一个顶层 await 特性有关,顶层 await 本人并不是很了解,以后可能会出一篇文章进行详细讨论,眼下是先解决这个问题

注意到错误后面的兼容性问题,于是本人猜想是与打包兼容的问题相关,又是回到文档看了一圈,发现与打包兼容相关的配置是在 vite 文档内。于是继续顺着找,找到了打包目标的配置项,文档:https://vitejs.dev/config/build-options.html#build-target

根据官网的写法与顶层 await 特性的兼容表,我将配置写成了下面的样子:

// nuxt.config.ts
export default {
  // ... other options
  vite: {
    build: {
      target: ["chrome92", "firefox92", "edge89", "safari15", "opera75"],
    }
  },
}

注册全局指令

页面中有一个需求是监听移动端左右滑动事件,但是 vue 原生的监听事件是分了两个 listener,所以需要封装一个指令,使用了该指令的 Dom 元素可以触发左右滑动指令与其回调

在 Nuxt 中我们使用其提供的上下文环境进行指令的注入:https://v3.nuxtjs.org/guide/directory-structure/plugins#vue-directives

核心写法几乎跟 Vue3 原版的写法一样,只不过就是通过其提供的上下文内的 vueApp 将指令注入到全局中使用

// plugins/TouchSlide.ts
export default defineNuxtPlugin(nuxt => {
  const TouchSlideDirective: ObjectDirective<HTMLElement, HTMLElement> = {
    mounted(el, binding: DirectiveBinding, vnode) {
      let StartX = 0, EndX = 0, { stop } = binding.modifiers
      el.addEventListener('touchstart', (e) => {
        StartX = e.touches[0].clientX
        if (stop) e.stopPropagation()
      })

      el.addEventListener('touchend', (e) => {
        EndX = e.changedTouches[0].clientX
        const moveX = StartX - EndX
        if (stop) e.stopPropagation()

        if (moveX < -100) binding.value && binding.value({ direction: 'right' })
        else if (moveX > 100) binding.value && binding.value({ direction: 'left' })
      })
    }
  }
  nuxt.vueApp.directive('TouchSlide', TouchSlideDirective)
})

总结

整个使用体验还是不太好的,尤其是各种组件的引入,都需要花费很多时间处理兼容性问题,虽然官方已经于 2022 年 4 月 21 日 发布了正式版本,但是个人认为其 广泛应用到生产环境 中还需要相当一段长的时间进行打磨与处理,所以还需要一段时间进行观望,期待未来的时候 Nuxt3 能带给我们相对于 Nuxt2 更优质的开发体验与更友好的配置模式。

参考文章

个人信息
avatar
Shiinafan
文章
38
标签
52
归档
4
导航