[Nuxt系列01] Nuxt 简介

·  阅读 4400
[Nuxt系列01] Nuxt 简介

Vue3.0 发布在即,可以说既有“千呼万唤始出来”的期待,也有“学海无厌回头是岸苦作舟”的感慨。这大概是前端最好的时代。

书归正传,这里要说的是Vue的更上层建筑NuxtJs,一个基于Vue的服务端渲染(SSR,Server Side Render)应用框架。下文将简单介绍 Nuxt 及其目录结构,并加入一些个人浅见。

NuxtJs 是啥

自从入坑 NuxtJs,转眼之间一年多过去了,Nuxt 版本已由初入手时的 2.0.x 版本来到了现在的 2.11.x 版本并继续火速迭代中,而我们的线上版本在无重大 bug 的前提下便“永恒”维持在 2.4.x 版本,于是很多新的功能或特性只有眼馋的份。就一个商业项目来说,自然万事求稳。关于如此小心翼翼的个中痛点及可能的解决方案,将在之后的系列文章述及。

如题头所述,Nuxt 是基于 Vue 的 SSR 应用框架,对标 React 的 Next 框架。细节可参见Vue SSR 指南

Nuxt 通过对客户端/服务端基础架构的抽象组织,使开发者能够像之前开发 Vue 应用一样继续愉快地撸代码,用惯了 Vue 全家桶的小伙伴大概都可以无痛迁移。同时 Nuxt 在 SSR 之外也提供了 SPA 和静态化能力,充分满足你的各种口味,所以它称自己为“通用应用框架”。

之于 SSR:Nuxt 提供页面级的 asyncDatafetch 方法在(服务端/客户端)组件渲染之前异步获取数据并更新页面 data 以及 store 中的数据;提供页面级的 head 方法和 nuxt.config.js 中的 head 属性来配置当前页面/全局的头部标签。以此来实现对 seo 的需求。

lifecycle

更多细节参见官方文档。值得万分肯定的,Nuxt 和 Vue 一样提供了相当详实友好的文档体系,方便各位小伙伴们能够快速入门。

初始化一个 Nuxt 应用

Nuxt 提供了脚手架工具create-nuxt-app方便开发者快速开始一个 Nuxt 应用,就像 Vue Cli 一样。期间它会让你选择服务端框架、UI框架、测试框架,并询问添加网络请求模块(Axios)、代码风格检查工具(Eslint 等)。我们运行下面的指令:

$ npx create-nuxt-app my-nuxt-app
复制代码

并选择 express 作为 server 端框架,得到如下目录结构的“新手村装备”,官方称其为新手模版:

├─ assets                资源目录
├─ components            组件目录
├─ layout                布局目录
├─ middleware            中间件目录
├─ node_modules          项目依赖
│
├─ pages                 页面目录
│   └─ index.vue         首页
│
├─ plugins               插件目录
├─ server                express server
│   └─ index.js          server入口文件
│
├─ static                静态文件目录
├─ store                 vuex状态树目录
├─ .gitignore            git忽略文件
├─ package.json          项目配置文件
├─ nuxt.config.js        nuxt配置文件
└─ README.md             项目说明文档
复制代码

在此基础上,我们对其进行添砖加瓦,得到如下的进阶版目录结构:

├─ apis                  项目api目录
├─ assets                资源目录
├─ components            组件目录
│    ├─ baseComponents   项目基础组件
│    ├─ commonComponents 项目公共组件
│    └─ pageComponents   项目页面级组件
├─ layout                布局目录
├─ middleware            中间件目录
├─ node_modules          项目依赖
├─ nuxtdist              项目打包目录
│
├─ pages                 页面目录
│   └─                   ......
│
├─ plugins               插件目录
├─ server                express server
│   ├─ config            server配置文件
│   ├─ controller        server控制器
│   ├─ routes            server路由文件
│   ├─ utils             工具方法
│   └─ index.js          server入口文件
│
├─ static                静态文件目录
├─ store                 vuex状态树目录
├─ .eslintrc.js          eslint配置文件
├─ .gitignore            git忽略文件
├─ package.json          项目配置文件
├─ nuxt.config.js        nuxt配置文件
├─ pm2.config.json       pm2守护进程配置文件
└─ README.md             项目说明文档
复制代码

首先来瞄一眼 package.json

{
  "name": "my-nuxt-app",
  "version": "1.0.0",
  "description": "My gnarly Nuxt.js project",
  "author": "",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    "build": "nuxt build",
    "start": "cross-env NODE_ENV=production node server/index.js",
    "generate": "nuxt generate"
  },
  "dependencies": {
    "nuxt": "^2.0.0",
    "cross-env": "^5.2.0",
    "express": "^4.16.4"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}
复制代码

可以看到,项目默认使用了 cross-env 来抹平不同系统对于环境变量存取的差异,并且 nuxt 版本仍然维持在“古老”的 2.0.0 版本,这里建议直接升级到 2.5.x 以上的版本(直接最新稳定版多爽快),之前的某些版本中曾出现过 build 产出文件缺失的问题,具体的可以追踪一下这个 issue

接下来,运行 npm run dev 指令,应用即运行在本地 3000 端口并开启热重载功能:

my-nuxt-app
下面就可以愉快地玩耍了~

1、pages及路由的组织

首先,一个大前提,Nuxt 依据 pages 目录结构自动生成 vue-router 的路由配置。

这意味着在常规 Nuxt 开发过程中,开发者无法更多干涉路由配置,只能通过合理安排 pages 下的目录结构达成合理规划路由的目的,这无疑丧失了一部分灵活性,但同样形成了一定的强约束效果,使我们必须规范组织目录层级、合理命名目录及页面组件。更不必说还需考虑 SEO 问题,一旦路由(尤其是那些需要搜索引擎收录的)确定便轻易不再更改。

我们简单约定,pages 文件夹和页面组件的命名上尽量采用简单清晰符合语义及使用场景的全小写命名,在此基础上合理组织页面级组件并考虑后期扩展或改造的可能性。

比如一个个人中心页面,可以有如下的目录结构:

├─ pages/                
│   └─ user/        
│       ├─ index.vue   
│       └─ msg/        
│           ├─ index.vue    
│           └─ detail
│               └─ _id.vue
复制代码

于是我们可以通过访问http://localhost:3000/user/msg进入用户消息列表页面,再通过访问http://localhost:3000/user/msg/detail/xxxx进入具体的消息详情页面。

2、组件

在 pages/ 目录的约束前提下,应用的页面组件之外的其他组件便不得存放在该目录下。于是便被迫愉快地在模板提供的 components/ 目录下进行组织吧。

我们大致上将组件划分为以下这几种类型:供整个项目使用的基础组件,如 button、input 等针对特定 UI 和业务逻辑进行的组件封装;供多个页面使用的公共组件,如 Header、Footer 等;对具体页面进行组件拆分形成的组件,如首页中的各个部分。目录结构如下:

├─ components/            组件目录
│    ├─ baseComponents/   基础组件
│    ├─ commonComponents/ 公共组件
│    └─ pageComponents/   页面级组件
复制代码

并进一步对组件的命名进行约束:components/ 下的组件必须以大驼峰格式命名;对于基础组件 必须以 Base 作为命名前缀;对于 公共组件,划分为 pc 和移动两部分,适用于pc的组件必须以 Pc 作为命名前缀,适用于移动端的组件必须以 M 作为命名前缀,公共的组件则以 Common 作为命名前缀; 页面级组件 的命名需体现与具体页面的业务相关性。所有组件乃至其他的任何命名,都应尽可能语义化。

3、layout布局组件

layout
如上图所示,来自 Nuxt 官方文档的视图结构示意图。 如下图所示,来自实际项目的 DOM 树部分截图:
nuxt-dom

layout 的默认布局代码如下:

<template>
  <nuxt/>
</template>
复制代码

我们通过在 layouts/ 文件夹下添加 default.vue组件对其进行扩展,比如可以插入各页面公用的 header 和 footer,设置统一的背景色,加入一些通用的业务逻辑等。

layout/ 下另一个比较必要的则是对错误页面的定制(如有必要),来处理当遇到 404、500 及其他错误时的 UI 展现。我们通过在 layouts/ 下添加 error.vue 组件来扩展 error 页面,它默认长这个样子:

error-page

而对于自定义布局组件,则结合应用的具体业务场景进行组织拆分,比如对于用户登陆、注册、改密等一系列相关页面,为它们提供公用的 layout,但这也意味着页面组件与 layout 之间形成了一定程度的耦合,在后期的维护升级过程中难免形成掣肘。所以随着应用的成长,任何不再满足需求的 layout 都应遭到舍弃并重构代码,而不应该无休止地增加复杂度。适时重构也应是在任何产品生命周期中都应被一以贯之的准则之一。

4、关于项目 api 的统一组织

接下来,我们新建了 apis/ 文件夹,在其中存放了整个项目各个功能模块的 API 列表,通过封装一个 request.js 的模块得以将所有 API 统筹至一起,方便统一管理。大致如下:

assets/js/request.js:

import axios from 'axios'
import qs from 'qs'
let request = axios.create({
  baseURL: 'https://xxx.xxx',
  timeout: xxxx,
  headers: { ... },
})

request.interceptors.request.use(
  (config) => {
    config.data = qs.stringify(config.data)
    return config
  }, 
  (err) => {
    return Promise.reject(err)
  }
)

export default request;

复制代码

apis/login.js:

import request from '~/assets/js/request.js'

export const getCaptcha = () => {
  return request({ method: 'GET', url: '/login/captcha', responseType: 'blob' })
}

)
// ...

复制代码

相信在网上可以摸到很多如上的相似解决方案。但在实际的开发过程中,可以感受到这样的组织形式并不能很好地为 nuxt 服务。最直观的是,这样操作并没有使用到@nuxtjs/axios作为 Axios 集成至 Nuxt 的版本所提供的服务端特性,而只是单纯地像在其他非 ssr 的项目中那样使用 Axios,比如无法使用注入到应用上下文对象 context 中作为其属性一员的 $axios,更不用说访问其他的 context 属性来做一些逻辑上的处理。 甚至因受限于 ssr,我们无法为 Axios 定义拦截器,因为普通的 axios 无法侵入到服务端,例如,我们无法为在服务端请求的接口需要跳转页面时调用 redirect 方法。

总之,我们应该尝试使用 @nuxtjs/axios 而非 axios 作为 Nuxt 应用的网络请求模块,并寻求一种合理的 API 组织方式。Organize and decouple your API calls in Nuxt.js 一文提供了可能的方案,后续文章中将尝试给出译文以及基于文章思想的解决方案。

5、server 入口文件和 Nuxt 配置文件 nuxt.config.js

这里我们选择 express 作为 b 端框架来提供服务端渲染能力,入口文件大概是这个样子的:

const express = require('express')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = express()

// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'

async function start () {
  // Init Nuxt.js
  const nuxt = new Nuxt(config)

  const { host, port } = nuxt.options.server

  // Build only in dev mode
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  } else {
    await nuxt.ready()
  }

  // Give nuxt middleware to express
  app.use(nuxt.render)

  // Listen the server
  app.listen(port, host)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}
start()

复制代码

而 Nuxt 配置文件的基础款长这个样子:

module.exports = {
  mode: 'universal',
  /*
  ** Headers of the page
  */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#fff' },
  /*
  ** Global CSS
  */
  css: [
  ],
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
  ],
  /*
  ** Nuxt.js dev-modules
  */
  buildModules: [
  ],
  /*
  ** Nuxt.js modules
  */
  modules: [
  ],
  /*
  ** Build configuration
  */
  build: {
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    }
  }
}

复制代码

确实是超基础白板“新手装”,后续可对照官方文档进行进行需要配置,比如公共的 meta 元信息、reset 样式及公共样式、反向代理、代码拆分及压缩等等。不论如何,现在已经可以开始尝试进行 Nuxt 应用开发了。


后续将围绕开发及发布上线过程中曾遇到的痛点继续展开,欢迎指点、交流~

原文链接

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改