服务端渲染SSR-Nuxt.js

服务端渲染SSR->Nuxt.js

本文中的npm/yarn的使用取决于用户自身,这里只截取了官网使用的安装方法。

在开始正题之前,首先我们要明白:

什么是服务端渲染,为什么需要服务端渲染。

通常的Vue应用渲染的做法是,通过vue-loader等加载器直接在浏览器上输出Vue组件和DOM并进行DOM操作。

但这样做有几个弊端:

  1. 由于所有的一切都是渲染上去的,初始的html界面只有一个基本结构<div></div>,导致绝大部分搜索引擎在进行页面内容抓取的时候搜索不到任何内容,也就无法在搜索引擎中搜索到本页面(google和bing支持抓取渲染页面)或是异步处理后的信息。虽然对很多管理系统而言本身并不需要出现在搜索引擎之中,但如今SPA已经应用到类似博客、说明文档之类的页面之上,它们是需要依赖搜索引擎的,因此有必要进行SEO(搜索引擎优化)使其暴露在搜索引擎之下。
  2. Vue应用进行浏览器渲染时,在首屏就需要将所有运行依赖和项目文件加载(可以通过按需加载优化一些),当依赖很庞大的时候会导致首屏加载速度很慢,即使使用了CDN + gzip依然可能导致首屏加载时长时间的白屏,降低了用户体验。

服务端渲染就是为了解决这两个问题诞生的,尽管它会带来开发环境(需要node.js)和开发条件(部分外部依赖库需要特殊处理)的限制和服务器更大的压力负载,但在很多应用场景之中已经有了广泛的使用。

服务器渲染会在服务器将Vue组件渲染成html并将整个html文本传输到浏览器,再由浏览器利用原生html生成DOM并激活其可交互性。

Vue原生服务端渲染

安装依赖

Vue原生服务端渲染需要以下依赖:

  • vue & vue-server-renderer 2.3.0+
  • vue-router 2.5.0+
  • vue-loader 12.0.0+ & vue-style-loader 3.0.0+

其中vue-server-renderer不是vue-cli自动工具创建时自动安装的依赖,需要自己安装。

npm install vue vue-server-renderer --save

注意,vue-server-renderer使用有以下要求:

  1. node.js > v6.0
  2. 必须与vue版本匹配
  3. 需要依赖node.js模块,因此只能运行在node环境中
用法

下面是一个vue-server-renderer的使用例子:

// 第 1 步:创建一个 Vue 实例
const Vue = require('vue')
const app = new Vue({
    
    
  template: `<div>Hello World</div>`
})

// 第 2 步:创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer()

// 第 3 步:将 Vue 实例渲染为 HTML
renderer.renderToString(app, (err, html) => {
    
    
  if (err) throw err
  console.log(html)
  // => <div data-server-rendered="true">Hello World</div>
})

// 在 2.5.0+,如果没有传入回调函数,则会返回 Promise:
renderer.renderToString(app).then(html => {
    
    
  console.log(html)
}).catch(err => {
    
    
  console.error(err)
})

关于Nuxt.js

Nuxt.js是借鉴了Next.js(react的服务端渲染框架)产生的Vue.js服务端渲染框架。主要聚焦于UI渲染。并集成、拓展、简化了很多Vue.js中有用的特性,如异步加载数据、中间件支持、热加载、自动路由生成等。

下面是一个完整的Nuxt.js服务器请求到渲染(或切换路由导致的渲染)的流程:

nuxt-schema
安装

目前脚手架版本为v4,v3脚手架构建的项目与v4项目有很大不同,主要体现在服务器端。由于官网不推荐直接修改服务端文件,而推荐使用服务端中间件来进行服务器端的处理,因此取消了server文件夹和server.config.js配置文件夹。

官网推荐使用脚手架工具构建。

npm install -g create-nuxt-app

安装完成后在命令行执行

npx create-nuxt-app <项目名>

yarn create nuxt-app <项目名>

执行后会触发一系列选项,依次选择即可。若要使用ts构建在使用语言一项选择Typescript即可。

项目构建完成会自动安装依赖。

约定

Nuxt是约定优于配置的框架(有点类似Spring boot),对项目的目录结构有严格要求,其完整的目录结构应该如下:

project/
|-src/
  |-assets/			//用于放置需要webpack编译的静态文件(如SASS、LESS、JS)
    |-xx.sass
  |-layouts/		//用于放置布局文件,默认加载layout/default.vue作为布局。布局文件中使用<nuxt /标签显示主体部分(即router-view)
    |-default.vue
  |-middleware/		//用于放置中间件文件(前端而非后端的中间件)
    |-xx.ts
  |-pages/			//用于放置视图页面,会根据目录中的子目录和.vue文件的命名自动生成路由
    |-xx.vue
    |-xx/
      |-xx.vue
    |-_xx.vue
  |-plugins/		//用于放置自己编写的插件
    |-xx.ts
  |-static/			//用于存放不需要webpack编译的静态文件(如图片、文本文档等)
    |-xx.png
  |-store/			//用于存放Vuex状态数文件,Nuxt中默认集成了Vuex的功能,若此目录下存在则开启Vuex功能
    |-xx.ts
|-nuxt.config.js	//用于组织Nuxt应用的个性化配置
|-package.json		//用于描述应用的依赖关系和对外暴露的脚本接口

默认的目录别名为:

~或@对应srcDir

~~或@@对应rootDir

一般情况下srcDir和rootDir相同

如果需要引入静态文件目录(assets或static),使用/assets/xx或/static/xx方式,但在Nuxt 2.0之后若要在css中引用,则需要使用~assets或@assets(没有斜杠)。

配置

可以在nuxt.config.js中配置Nuxt应用。但在使用create-nuxt-app构建应用时一般采用默认配置即可。详细配置可以参考官方文档。

路由

在页面中跳转可以使用<nuxt-link to="/"></nuxt-link>形式的标签,行为与<router-link to="/"></router-link>一致。

Nuxt的路由匹配规则如下:逐级匹配pages目录下次级,若次级为.vue文件,将其作为本级路由,组件作为本级页面;若次级为目录,将其作为本级路由,其下index.vue作为本级页面,其余文件作为次级路由及次级页面。

下面给出一个路由匹配规则的例子:

pages目录结构:

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

生成的路由配置:

router: {
    
    
  routes: [
    {
    
    
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
    
    
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
    
    
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

也可以利用Nuxt的路由匹配规则设置动态路由,例子如下:

目录结构:

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

生成的路由配置:

router: {
    
    
  routes: [
    {
    
    
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
    
    
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
    
    
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
    
    
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}

可以看见 users-id 的路由路径带有 :id? 参数,表示该路由是可选的。如果你想将它设置为必选的路由,需要将 users/_id 作为目录,并在目录内创建一个 index.vue 文件。

也可以生成嵌套路由:

目录结构:

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

生成的路由配置:

router: {
    
    
  routes: [
    {
    
    
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
    
    
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
    
    
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

创建嵌套子路由需要同级目录下同名的一对文件和目录,此时Nuxt会将目录中的文件作为子路由。

也可以结合上述动态路由和嵌套路由形成动态嵌套路由,在此不多赘述,详情请参考官网。

本级目录下可以定义特殊的 _.vue 文件,若路径不匹配本级的所有路由,则进入 _.vue ,通常用作404页面。

视图和布局

vue-router中并没有区分顶级视图和嵌套视图,使得视图多重嵌套的时候可能导致层级不清。因此Nuxt中做出了区分,使用 <nuxt /> 标签渲染顶级视图, <nuxt-child /> 渲染嵌套视图。

Nuxt中以 layouts/default.vue 为默认布局,也可以在组件中使用 layout 属性定义当前组件的布局文件。例如:

<template>
  <!-- Your template -->
</template>
<script>
  export default {
     
     
    layout: 'blog'
    // page component definitions
  }
</script>

layouts目录下还包含一个特殊的 error.vue 文件,虽然处于layouts目录下但它是一个页面,用作显示错误页面的组件。

Nuxt中使用命名视图首先需要像使用vue-router那样在页面或布局中使用 <nuxt name="top"/><nuxt-child name="top"/> 以加载命名视图;其次为了指定页面的命名视图需要在 nuxt.config.js 文件中扩展路由配置:

export default {
    
    
  router: {
    
    
    extendRoutes(routes, resolve) {
    
    
      const index = routes.findIndex(route => route.name === 'main')
      routes[index] = {
    
    
        ...routes[index],
        components: {
    
    
          default: routes[index].component,
          top: resolve(__dirname, 'components/mainTop.vue')
        },
        chunkNames: {
    
    
          top: 'components/mainTop'
        }
      }
    }
  }
}

以上代码在main组件中拓展了top命名视图。

Nuxt为 .vue 组件增加了一些特殊的配置项。包括:

属性名 描述
asyncData 最重要的一个键, 支持 异步数据处理,另外该方法的第一个参数为当前页面组件的 上下文对象
fetch asyncData 方法类似,用于在渲染页面之前获取数据填充应用的状态树(store)。不同的是 fetch 方法不会设置组件的数据。详情请参考 关于 fetch 方法的文档
head 配置当前页面的 Meta 标签, 详情参考 页面头部配置 API
layout 指定当前页面使用的布局(layouts 根目录下的布局文件)。详情请参考 关于 布局 的文档
loading 如果设置为false,则阻止页面自动调用this.$nuxt.$loading.finish()this.$nuxt.$loading.start(),您可以手动控制它,请看例子,仅适用于在 nuxt.config.js 中设置loading的情况下。请参考API 配置 loading 文档
transition 指定页面切换的过渡动效, 详情请参考 页面过渡动效
scrollToTop 布尔值,默认: false。 用于判定渲染页面前是否需要将当前页面滚动至顶部。这个配置用于 嵌套路由的应用场景。
validate 校验方法用于校验 动态路由的参数。
middleware 指定页面的中间件,中间件会在页面渲染之前被调用, 请参考 路由中间件

详细配置请参考官方文档。

异步数据

Nuxt可以使用组件页面内置的asyncData方法在组件每次加载前(beforeMount,和Vue3.0中的setup()函数处于同一个生命周期)调用。方法的第一个参数为当前的上下文对象。它所返回的数据会递交给当前组件(和setup()一样)。

asyncData方法可以选择返回一个Promise对象或使用async/await来处理异步请求。Nuxt中封装了axios模块,但使用方法与axios有所不同,具体差异请参考 nuxt/axios官方文档。若要使用原本的axios依赖,必须在 axios.create 实例化axios对象后再添加 axios.interceptors 拦截器,不然会导致多次刷新页面情况下服务器渲染多次加载拦截器导致数据处理出错。

asyncData中的上下文对象API具体参考官方文档。

插件和模块

Nuxt中插件的使用方式与普通Vue项目没有区别,只是添加了额外的注入方法。

注入Vue实例可以在nuxt.config.js配置:

export default {
    
    
  plugins: ['~/plugins/vue-inject.js']
}

之后可以与 Vue.prototype.$xx 这种方式一样使用其中的方法。

也可以在plugins目录下编写自己的插件,其基本语法为:

export default ({
    
     app }, inject) => {
    
    
  // Set the function directly on the context.app object
  app.myInjectedFunction = string =>
    console.log('Okay, another function', string)
}

此时这个插件在nuxt.config.js注册后可以在context中使用:

export default {
    
    
  plugins: ['~/plugins/ctx-inject.js']
}
export default {
    
    
  asyncData(context) {
    
    
    context.app.myInjectedFunction('ctx!')
  }
}

若想要注册到全局可以使用方法中的第二个参数inject:

export default ({
    
     app }, inject) => {
    
    
  inject('myInjectedFunction', string => console.log('That was easy!', string))
}

此时注册后可以在Vue实例,context,Vuex中使用:

export default {
    
    
  plugins: ['~/plugins/combined-inject.js']
}
export default {
    
    
  mounted() {
    
    
    this.$myInjectedFunction('works in mounted')
  },
  asyncData(context) {
    
    
    context.app.$myInjectedFunction('works with context')
  }
}
export const state = () => ({
    
    
  someValue: ''
})

export const mutations = {
    
    
  changeSomeValue(state, newValue) {
    
    
    this.$myInjectedFunction('accessible in mutations')
    state.someValue = newValue
  }
}

export const actions = {
    
    
  setSomeValueToWhatever({
    
     commit }) {
    
    
    this.$myInjectedFunction('accessible in actions')
    const newValue = 'whatever'
    commit('changeSomeValue', newValue)
  }
}

可以额外在plugins选项中使用 ssr:false 指定是否在服务端使用。

在Nuxt 2.4之后ssr选项会被逐渐弃用,使用mode: 'client’或mode: 'server’来取代它。也可以使用xx.server.js文件命名来指定服务端插件或使用xx.client.js指定客户端插件。

Nuxt也提供了一些官方模块:

  • @nuxt/http: 基于ky-universal的轻量级和通用的 HTTP 请求
  • @nuxtjs/axios: 安全和使用简单 Axios 与 Nuxt.js 集成用来请求 HTTP
  • @nuxtjs/pwa: 使用经过严格测试,更新且稳定的 PWA 解决方案来增强 Nuxt
  • @nuxtjs/auth: Nuxt.js 的身份验证模块,提供不同的方案和验证策略

社区中的模块列表可以在 https://github.com/topics/nuxt-module 中查询。

Vuex

若项目的store目录存在,则Nuxt会默认引用并配置加载Vuex。

Nuxt推荐使用模块方式构建状态树,但它也支持单文件构建状态树。其中state必须写成箭头函数形式以避免返回引用类型带来的多个实例间的相互影响,并保证获取到正确的上下文this。

模块构建状态树的形式中,index.js将作为根模块。下面是一个构建的例子:

store/index.js

export const state = () => ({
    
    
  counter: 0
})

export const mutations = {
    
    
  increment(state) {
    
    
    state.counter++
  }
}

store/todos.js

export const state = () => ({
    
    
  list: []
})

export const mutations = {
    
    
  add(state, text) {
    
    
    state.list.push({
    
    
      text,
      done: false
    })
  },
  remove(state, {
    
     todo }) {
    
    
    state.list.splice(state.list.indexOf(todo), 1)
  },
  toggle(state, todo) {
    
    
    todo.done = !todo.done
  }
}

则Vuex会被这样创建:

new Vuex.Store({
    
    
  state: () => ({
    
    
    counter: 0
  }),
  mutations: {
    
    
    increment(state) {
    
    
      state.counter++
    }
  },
  modules: {
    
    
    todos: {
    
    
      namespaced: true,
      state: () => ({
    
    
        list: []
      }),
      mutations: {
    
    
        add(state, {
    
     text }) {
    
    
          state.list.push({
    
    
            text,
            done: false
          })
        },
        remove(state, {
    
     todo }) {
    
    
          state.list.splice(state.list.indexOf(todo), 1)
        },
        toggle(state, {
    
     todo }) {
    
    
          todo.done = !todo.done
        }
      }
    }
  }
})

也可以将Vuex状态树分割成state.js、mutation.js、action.js、getters.js,并使用index.js维护他们。

Nuxt中Vuex的插件使用方式如下(仅在模块方式下支持):

import myPlugin from 'myPlugin'

export const plugins = [myPlugin]

export const state = () => ({
    
    
  counter: 0
})

export const mutations = {
    
    
  increment(state) {
    
    
    state.counter++
  }
}

由于Nuxt 3.0计划取消对非模块模式(即在一个单独index.js文件中构建状态树)的支持,因此不再介绍非模块模式。

运行时设置(2.1.3+支持)

nuxt.config.js中支持使用 publicRuntimeConfig: {}privateRuntimeConfig: {} 进行运行时环境变量的配置,其中 publicRuntimeConfig: {} 中的内容在客户端和服务端都可以通过 $config 全局变量获取到,而 privateRuntimeConfig: {} 的内容只有在服务端可以通过 $config 全局变量获取。

也可以像Vue-cli构建的项目中一样使用.env配置文件来进行配置。.env文件中的配置可以通过process.env.xx获取,也同样可以通过$config获取。

Typescript支持

Nuxt的Typescript支持包含三个依赖套件:

@nuxt/types:包含Nuxt的Typescript类型定义。

@nuxt/typescript-build:能让Nuxt在layouts,components,plugins和middlewares中使用Typescript。

@nuxt/typescript-runtime:为nuxt.config配置文件、本地modules和serverMiddlewares提供Typescript的runtime支持。

注意:所有的Typescript支持套件只能用于Nuxt 2.10或更高版本。

编译时依赖在create-nuxt-app构建工具构建时已经安装并配置完成。可以自己尝试构建一个Nuxt+Typescipt项目查看,这里就不赘述了。

不过需要注意如果通过编程式方式创建一个自定义的服务器框架的话需要确保Nuxt在构建完成之前已经准备好。

@nuxt/typescript-build在依赖设置中可以添加以下选项:

typeCheck: boolean | object

是否在单独的进程中启用TS类型检查,默认值为true,也可以通过传入object覆盖选项,详情请查看fork-ts-checker-webpack-plugin

ignoreNotFoundWarnings: boolean

是否隐藏"export xxx was not found"警告,默认false。

如果想要添加额外的TS加载器选项,可以通过loaders.ts和loaders.tsx两个文件分别对ts和tsx文件进行自定义。

安装

若使用create-nuxt-app工具构建且过程中选择了TS作为开发语言则上述依赖会自动安装并配置。

若自己构建则需要按如下过程进行配置:

  1. 安装依赖(也可以使用npm)

    yarn add --dev @nuxt/typescript-build @nuxt/types
    
  2. 配置

    需要在nuxt.config.js文件中的buildModules中加入@nuxt/typescript-build

    export default {
      buildModules: ['@nuxt/typescript-build']
    }
    

    而后创建tsconfig.json文件,以下是推荐配置:

    {
      "compilerOptions": {
        "target": "ES2018",
        "module": "ESNext",
        "moduleResolution": "Node",
        "lib": [
          "ESNext",
          "ESNext.AsyncIterable",
          "DOM"
        ],
        "esModuleInterop": true,
        "allowJs": true,
        "sourceMap": true,
        "strict": true,
        "noEmit": true,
        "baseUrl": ".",
        "paths": {
          "~/*": [
            "./*"
          ],
          "@/*": [
            "./*"
          ]
        },
        "types": [
          "@types/node",
          "@nuxt/types"
        ]
      },
      "exclude": [
        "node_modules"
      ]
    }
    

    注意,若要使用Optional ChainingNullish Coalescing 语法需要将target设置为ES2018,设置成ESNext时不会如期运作因为ESNext尚未支持。

    再加入一个声明文件为Vue提供支持:

    declare module "*.vue" {
      import Vue from 'vue'
      export default Vue
    }
    
运行时依赖(可选依赖)

由于nuxt.config文件、本地modules和服务端中间层不会经由TS编译,因此需要TS运行时依赖的支持。

Nuxt因此创建了@nuxt/typescript-runtime,此包提供一个二进制的nuxt-ts文档,会在运行前注册ts-node。

yarn add @nuxt/typescript-runtime

注意:本依赖由于是运行时依赖,所以必须作为dependency

之后更改package.json文件:

"scripts": {
  "dev": "nuxt-ts",
  "build": "nuxt-ts build",
  "generate": "nuxt-ts generate",
  "start": "nuxt-ts start"
},
"dependencies": {
  "@nuxt/typescript-runtime": "latest",
  "nuxt": "latest"
},
"devDependencies": {
  "@nuxt/types": "latest",
  "@nuxt/typescript-build": "latest"
}

之后可以在上述三种文件中使用TS了。

代码检查

注意:若通过Nuxt的脚手架安装中选择TS作为开发语言则本依赖已使用默认配置集成。

安装@nuxtjs/eslint-config-typescript:

yarn add -D @nuxtjs/eslint-config-typescript

然后创建.eslintrc.js文件以拓展@nuxtjs/eslint-config-typescript,下面是默认配置:

module.exports = {
  extends: [
    '@nuxtjs/eslint-config-typescript'
  ]
}

注意:此依赖会使用@typescript-eslint/parser作为TS语法分析器,所以请确保parserOptions.parser选项不会被其他拓展设置覆盖。且若要使用本依赖需要先移除@nuxtjs/eslint-config,因为其使用的babel-eslint作为语法分析器。

最后编辑package.json文件,添加lint指令:

"lint": "eslint --ext .ts,.js,.vue ."

就可以通过npm run lint来检查TS文件了。

如果需要自定义TS Eslint规则,可以查看这里

如果想设置TS运行时检查,可以在Nuxt配置文件的typeCheck模块选项中启用fork-ts-checker-webpack-plugin

export default {
  typescript: {
    typeCheck: {
      eslint: {
        files: './src/**/*.{ts,js,vue}'
      }
    }
  }
}

猜你喜欢

转载自blog.csdn.net/qq_41575208/article/details/118336224