服务端预渲染之Nuxt - (爬坑篇)

Nuxt是解决SEO的比较常用的解决方案,随着Nuxt也有很多坑,每当突破一个小技术点的时候,都有很大的成就感,在这段时间里着实让我痛并快乐着。在这里根据个人学习情况,所踩过的坑做了一个汇总和总结。

Nuxt开发跨域

项目可以使用Nginx来反向代理,将外来的请求(这里也注意下将Linux的防火墙放行相应端口)转发的内部Nuxt默认的3000端口上,最简单的配置文件如下:

nuxtjs.config.js

{
    modules: [
      '@nuxtjs/axios',
      '@nuxtjs/proxy'
    ],
    proxy: [
        [
            '/api',
            {
              target: 'http://localhost:3001', // api主机
              pathRewrite: { '^/api' : '/' }
            }
        ]
    ]
}

@nuxtjs/proxy需要手动单独安装。

Nuxt Store 使用

Nuxt中使用Vuex跟传统在Vue中使用Vuex还不太一样,首先Nuxt已经集成了Vuex,不需要我们进行二次安装,直接引用就好,在默认Nuxt的框架模板下有一个Store的文件夹,就是我们用来存放Vuex的地方。

Nuxt官方也提供了相关文档,可以简单的过一下,但是官方文档我看来比较潦草。

根据官方文档在store文件下面创建两个.js文件,分别是index.jstodo.js。并在pages文件夹下面创建index.vue

store - index.js

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

store - todo.js

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

pages - index.vue

<template>
  <section class="container">
    <div>
      <h2 @click="$store.commit('increment')">{{counter}}</h2>
      <ul>
        <li v-for="(item,index) of list"
            :key="index">{{item.text}}</li>
      </ul>
    </div>
  </section>
</template>

<script>
import Logo from '~/components/Logo.vue'
import {mapState} from "vuex";
export default {
  components: {
    Logo
  },
  computed:{
    ...mapState(["counter"]),
    ...mapState("todos",{
      list:state => state.list
    })
  },
  created(){
    for(let i =0;i<10;i++){
      this.$store.commit("todos/add",i);
    }
    console.log(this.list)
  }
}
</script>

Nuxt中可以直接使用this.$store,并且是默认启用命名空间的。再看一下computed中的代码,在使用mapState的时候,counter属性是直接获取出来的,然而todos属性则是通过命名空间才获取到的。这又是怎么回事?

Nuxtstore中的index.js文件中所有的state、mutations、actions、getters都作为其公共属性挂载到了,store实例上,然而其他的文件则是使用的是命名空间,其对应的命名空间的名字就是其文件名。

运行项目的时候可以在.nuxt文件夹内找到store.js看下是怎么完成的。简单的解释一下代码作用,以及做什么用的。

.nuxt - store.js

//  引入vue
import Vue from 'vue'
//  引入vuex
import Vuex from 'vuex'
//  作为中间件
Vue.use(Vuex)
//  保存console 函数
const log = console
//  vuex的属性
const VUEX_PROPERTIES = ['state', 'getters', 'actions', 'mutations']
//  store属性容器
let store = {}
//  没有返回值的自执行函数
void (function updateModules() {
  // 初始化根数据,也就是上面所说的index文件做为共有数据
  store = normalizeRoot(require('@/store/index.js'), 'store/index.js')
  // 如果store是函数,提示异常,停止执行
  if (typeof store === 'function') {
    //  警告:经典模式的商店是不赞成的,并将删除在Nuxt 3。
    return log.warn('Classic mode for store is deprecated and will be removed in Nuxt 3.')
  }
  // 执行存储模块
  // store - 模块化
  store.modules = store.modules || {}
  // 解决存储模块方法
  //    引入todos.js 文件,即数据
  //    'todos.js' 文件名
  resolveStoreModules(require('@/store/todos.js'), 'todos.js')

  // 如果环境支持热重载
  if (process.client && module.hot) {
    // 无论何时更新Vuex模块
    module.hot.accept([
      '@/store/index.js',
      '@/store/todos.js',
    ], () => {
      // 更新的根。模块的最新定义。
      updateModules()
      // 在store中触发热更新。
      window.$nuxt.$store.hotUpdate(store)
    })
  }
})()

// 创建store实例
//   - 如果 store 是 function 则使用 store
//   - 否则创建一个新的实例
export const createStore = store instanceof Function ? store : () => {
  //  返回实例
  return new Vuex.Store(Object.assign({
    strict: (process.env.NODE_ENV !== 'production')
  }, store))
}
//  解决存储模块方法
//  moduleData  -   导出数据
//  filename    -   文件名
function resolveStoreModules(moduleData, filename) {
  // 获取导出数据,为了解决es6 (export default)导出
  moduleData = moduleData.default || moduleData
  // 远程store src +扩展(./foo/index.js -> foo/index)
  const namespace = filename.replace(/\.(js|mjs|ts)$/, '')
  // 空间名称
  const namespaces = namespace.split('/')
  // 模块名称(state,getters等)
  let moduleName = namespaces[namespaces.length - 1]
  // 文件路径
  const filePath = `store/${filename}`
  // 如果 moduleName === 'state'
  //  - 执行 normalizeState  - 正常状态
  //  - 执行 normalizeModule - 标准化模块
  moduleData = moduleName === 'state'
    ? normalizeState(moduleData, filePath)
    : normalizeModule(moduleData, filePath)
  // 如果是 (state,getters等)执行
  if (VUEX_PROPERTIES.includes(moduleName)) {
    // module名称
    const property = moduleName
    // 存储模块         //  获取存储模块
    const storeModule = getStoreModule(store, namespaces, { isProperty: true })
    // 合并属性
    mergeProperty(storeModule, moduleData, property)
    // 取消后续代码执行
    return
  }
  // 特殊处理index.js
  // 模块名称等于index
  const isIndexModule = (moduleName === 'index')
  // 如果等于
  if (isIndexModule) {
    // 名称空间弹出最后一个
    namespaces.pop()
    // 获取模块名称
    moduleName = namespaces[namespaces.length - 1]
  }
  // 获取存储模块
  const storeModule = getStoreModule(store, namespaces)
  // 遍历 VUEX_PROPERTIES
  for (const property of VUEX_PROPERTIES) {
    // 合并属性
    //  storeModule         -   存储模块
    //  moduleData[property]     -  存储模块中的某个属性数据
    //  property                -   模块名称
    mergeProperty(storeModule, moduleData[property], property)
  }
  // 如果moduleData.namespaced === false
  if (moduleData.namespaced === false) {
    // 删除命名空间
    delete storeModule.namespaced
  }
}
//  初始化根数据
//  moduleData  -   导出数据
//  filePath    -   文件路径
function normalizeRoot(moduleData, filePath) {
  // 获取导出数据,为了解决es6 (export default)导出
  moduleData = moduleData.default || moduleData
  // 如果导入的数据中存在commit方法,则抛出异常
  // - 应该导出一个返回Vuex实例的方法。
  if (moduleData.commit) {
    throw new Error(`[nuxt] ${filePath} should export a method that returns a Vuex instance.`)
  }
  // 如果 moduleData 不是函数,则使用空队形进行合并处理
  if (typeof moduleData !== 'function') {
    // 避免键入错误:设置在覆盖顶级键时只有getter的属性
    moduleData = Object.assign({}, moduleData)
  }
  //  对模块化进行处理后返回
  return normalizeModule(moduleData, filePath)
}
//  正常状态
//   - 模块数据
//   - 文件路径
function normalizeState(moduleData, filePath) {
  // 如果 moduleData 不是function
  if (typeof moduleData !== 'function') {
    //  警告提示
    //  ${filePath}应该导出一个返回对象的方法
    log.warn(`${filePath} should export a method that returns an object`)
    //  合并 state
    const state = Object.assign({}, moduleData)
    //  以函数形式导出state
    return () => state
  }
  //  对模块化进行处理
  return normalizeModule(moduleData, filePath)
}
//  对模块化进行处理
//  moduleData  -   导出数据
//  filePath    -   文件路径
function normalizeModule(moduleData, filePath) {
  // 如果module数据的state存在并且不是function警告提示
  if (moduleData.state && typeof moduleData.state !== 'function') {
    //  “state”应该是返回${filePath}中的对象的方法
    log.warn(`'state' should be a method that returns an object in ${filePath}`)
    // 合并state
    const state = Object.assign({}, moduleData.state)
    // 覆盖原有state使用函数返回
    moduleData = Object.assign({}, moduleData, { state: () => state })
  }
  // 返回初始化数据
  return moduleData
}
//  获取store的Model
//      -   storeModule         store数据模型
//      -   namespaces          命名空间名称数组
//      -   是否使用命名空间    默认值 为false
function getStoreModule(storeModule, namespaces, { isProperty = false } = {}) {
  //  如果 namespaces 不存在,启动命名空间,命名空间名称长度1
  if (!namespaces.length || (isProperty && namespaces.length === 1)) {
    //  返回model
    return storeModule
  }
  //  获取命名空间名称
  const namespace = namespaces.shift()
  //  保存命名空间中的数据
  storeModule.modules[namespace] = storeModule.modules[namespace] || {}
  //  启用命名空间
  storeModule.modules[namespace].namespaced = true
  //  添加命名数据
  storeModule.modules[namespace].modules = storeModule.modules[namespace].modules || {}
  //  递归
  return getStoreModule(storeModule.modules[namespace], namespaces, { isProperty })
}
// 合并属性
//  storeModule         -   存储模块
//  moduleData          -  存储模属性数据
//  property            -   模块名称
function mergeProperty(storeModule, moduleData, property) {
  // 如果 moduleData 不存在推出程序
  if (!moduleData) return
  // 如果 模块名称 是 state
  if (property === 'state') {
    //  把state数据分到模块空间内
    storeModule.state = moduleData || storeModule.state
  } else {
    // 其他模块
    // 合并到对应的模块空间内
    storeModule[property] = Object.assign({}, storeModule[property], moduleData)
  }
}

以上就是编译后的store文件,大致的意思就是对store文件进行遍历处理,根据不同的文件使用不同的解决方案,使用命名空间挂载model

页面loading

Nuxt有提供加载Loading组件,一下是配置。

nuxtjs.config.js

module.exports = {
   loading: { color: '#3B8070' }
}

Nuxt提供的loading不能满足项目需求,可能有的项目不需要这样加载动画,so~,就需要自己手动配置一个。添加一个loading组件 (官方示例如下,详情可看官方文档)引用该组件。

nuxtjs.config.js

module.exports = {
 loading: '~components/loading.vue'
}

一个小插曲在Nuxt中,~与@都指向的是根目录。

components/loading.vue

<template lang="html">
  <div class="loading-page" v-if="loading">
    <p>Loading...</p>
  </div>
</template>

<script>
export default {
  data: () => ({
    loading: false
  }),
  methods: {
    start () {
      this.loading = true
    },
    finish () {
      this.loading = false
    }
  }
}
</script>

第三方组件库

项目开发过程中,难免会用到组件库,与在Vue中使用的时候是太一样的,需要添加一些依赖才能正常使用。

plugins - element-ui.js

import Vue from 'vue';
import Element from 'element-ui';
import locale from 'element-ui/lib/locale/lang/en';
export default () => {
  Vue.use(Element, { locale })
};

nuxtjs.config.js

module.exports = {
   css: [
       'element-ui/lib/theme-chalk/index.css'
   ],
   plugins: [
       '@/plugins/element-ui',
       '@/plugins/router'
   ]
};

使用中间件

中间件Nuxt没有给出具体的使用文档,而是放入了一个编辑器。这一点我感觉到了一丝丝的 差异。为什么要这样。。。简单的研究了一下,弄明白了大概。

middleware中创建想要的中间件。这里借用一下官网的例子。

middleware - visits.js

export default function ({ store, route, redirect }) {
  store.commit('ADD_VISIT', route.path)
}

向上面这样就创建好了一个中间件,但是应该怎么使用呢?在使用的时候有两种方式,一种是全局使用,另一种是在页面中单独使用,文件名会作为其中间件的名称。

++全局使用++

nuxtjs.config.js

export default {
  router: {
    middleware: ['visits']
  }
}

页面中单独使用

export default {
  middleware: 'auth'
}

官网中在页面中的asyncData中有一段这样的代码。

export default {
    asyncData({ store, route, userAgent }) {
        return {
            userAgent
        }
    }
}

持续更新。。。

总结

Nuxt的学习曲线非常小,就像Vue框架一样,已经是一个开箱即用的状态,我们可以直接跨过配置直接开发。对配置有兴趣的可以在Vue官方文档找到SSR渲染文档。

原文地址:https://www.cnblogs.com/aaron---blog/p/10740474.html

时间: 2024-09-30 07:38:04

服务端预渲染之Nuxt - (爬坑篇)的相关文章

服务端预渲染之Nuxt(介绍篇)

现在前端开发一般都是前后端分离,mvvm和mvc的开发框架,如Angular.React和Vue等,虽然写框架能够使我们快速的完成开发,但是由于前后台分离,给项目SEO带来很大的不便,搜索引擎在检索的时候是在网页中爬取数据,由于单页面应用读取到的页面是几乎空白的,无法爬取到任何数据信息. <!DOCTYPE html> <html> <head> <meta charset=utf-8> <meta name=viewport content=&quo

使用 PHP 来做 Vue.js 的 SSR 服务端渲染

对于客户端应用来说,服务端渲染是一个热门话题.然而不幸的是,这并不是一件容易的事,尤其是对于不用 Node.js 环境开发的人来说. 我发布了两个库让 PHP 从服务端渲染成为可能.spatie/server-side-rendering 和 spatie/laravel-server-side-rendering适配 laravel 应用. 让我们一起来仔细研究一些服务端渲染的概念,权衡优缺点,然后遵循第一法则用 PHP 建立一个服务端渲染. 什么是服务端渲染 一个单页应用(通常也叫做 SPA

网页开发方式-从静态页面到服务端渲染

前言 网页的最初形式就是一个个静态页面,例如我们写了一个 html 文件,放在服务器上就可以供用户访问了. 而后网站变得需要展示更多的内容,让内容更加动态,因此需要接入数据库,配合数据库的内容做展示,于是诞生了 html 模板引擎,用于把动态的数据插入到 html 中,叫做动态页面,后面我会叫它为传统的动态页面,例如 java 的 Jsp,php 的 Smarty 和 node 的 Jade 等. 再后来伴随浏览器的发展,网页交互变得越来越复杂等一系列原因,前端技术突飞猛进,诞生了前后端分离的单

Vue.js与 ASP.NET Core 服务端渲染功能整合

http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gy?ngy?si 译者:oopsguy.com 我真的很喜欢在前端使用 Vue.js,Vue 服务端渲染直到第二个版本才被支持. 在本例中,我想展示如何将 Vue.js  服务端渲染功能整合 ASP.NET Core. 我们在服务端使用了 Microsoft.AspNetCore.SpaServices 包,该包提供 ASP.N

React(0.13) 服务端渲染的两个函数

1.React.renderToString 函数,  参数是组件,返回一个字符串 <!DOCTYPE html> <html> <head> <title>React JS</title> <script src="../build_0.13/react.js"></script> <script src="../build_0.13/JSXTransformer.js"&g

[译]利用React Router4实现的服务端直出渲染(SSR)

我们已经熟悉React 服务端渲染(SSR)的基本步骤,现在让我们更进一步利用 React RouterV4 实现客户端和服务端的同构.毕竟大多数的应用都需要用到web前端路由器,所以要让SSR能够正常的运行,了解路由器的设置是十分有必要的 基本步骤 路由器配置 前言已经简单的介绍了React SSR,首先我们需要添加ReactRouter4到我们的项目中 $ yarn add react-router-dom # or, using npm $ npm install react-router

react服务端渲染框架

客户端渲染 加载一个空的html页面,然后请求一个打包的js文件,然后再客户端执行这个js文件 动态生成html内容然后插入到DOM元素上,在源代码查询中也只能看到空的html文档 没有任何其他内容 服务端渲染 加载出来的就带有完整的html文档内容(同时带有数据) 流程: 浏览器发送请求 --> 服务器端运行react代码生成页面 --> 服务器端返回渲染的页 客户端渲染:react代码在浏览器上执行,消耗的是用户浏览器的性能 服务端渲染:react代码在服务器上执行,消耗的是服务器端的性能

react服务端渲染(九)proxy代理&amp;&amp;多级路由&amp;&amp;数据的脱水和注水

使用reducer之后 我们现如今无法使用reducer来替代createStore来实现服务端的渲染! 服务端渲染之后,客户端会再次渲染,因为我们的客户端创建的store为空.解决办法:在服务端渲染的时候将获取到的数据赋值一个全局变量,客户端创建的store以这个变量的值作为初始值. const Store = createStore(Rducer,window.info,applyMiddleware(thunk)); 中间层代理转发,我们的浏览器端渲染之前是直接发送'http://47.9

服务端模板注入(SSTI攻击)

服务端模板注入 1.模板注入原理 和常见Web注入的成因一样,也是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露.代码执行.GetShell 等问题.其影响范围主要取决于模版引擎的复杂性. <?php require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php'; Twig_Autoloader::register(true); $twig