Nuxt.js综合案例
- 案例介绍
- 项目初始化
- 创建项目
- 导入样式资源
- 布局组件
- 导入登录注册页面
- 导入剩余页面
- 用户个人资料页面
- 设置页面
- 创建文章页面
- 文章详情页面
- 处理顶部导航栏链接
- 处理导航链接高亮
- 封装请求模块
- 登录注册
- 实现基本登录功能
- 封装请求的方法
- 解析存储登录状态实现流程(jwt)
- 将登陆状态存储到容器中
- 登录状态持久化
- 处理页面访问权限
- 创建中间件
- 监听query参数改变
- 统一设置用户Token
- 文章发布时间格式化处理
- 设置页面meta优化SEO
- Nuxt.js发布部署
- 打包
- 最简单的部署方式
- 配置host+port
- 执行打包
- 使用PM2启动Node服务器
- 概念
- 使用
- pm2常见命令
- 自动化部署介绍
- 传统的部署方式
- 现代化的部署方式(CI/CD)
- 流程
- 准备自动部署内容
- 使用GitHub Actions实现自动部署
案例介绍
案例名称:reaWorld
- 一个开源的学习项目,目的就是帮助开发者快速学习新技能
- GitHub仓库
- 在线实例
案例相关资源 - 页面模板:https://github.com/gothinkster/realworld-starter-kit/blob/master/FRONTEND_INS
TRUCTIONS.md - 接口文档 https://github.com/gothinkster/realworld/tree/master/api
学习前提 - Vue.js使用经验
- Nuxt.js基础
- Mode.js,Webpack相关使用经验
学习收货
- 掌握使用Nuxt.js开发同构渲染应用
- 增强Vue.js实践能力
- 掌握同构渲染中常见的功能处理
- 用户状态管理
- 页面访问权限处理
- SEO优化
- 掌握同构渲染应用的发布与部署
项目初始化
创建项目
mkdir realworld-nuxt.js
npm init -y
npm i nuxt
配置启动脚本
创建pages目录,配置初始化页面
导入样式资源
- 导入页面模板
app.html - 导入样式资源
- 配置布局组件
- 配置页面组件
布局组件
- 网站具有公共的头部和尾部,创建layout目录下的index.vue,我们想修改Nuxt.js的默认路由规则,在nuxt.config.js中配置
知识点, 删除nuxt默认的路由配置 routes.splice(0)
导入登录注册页面
- 登录/注册页面我们使用一个组件来完成
- 因为默认的路由被我们手动删除了routes.splice(0),所以我们要在nuxt.config.js中配置路由规则
- 上面的是登录页面,我们在配置注册页面的路由
- 在login页面添加一个计算属性isLogin来判断是登录页还是注册页
导入剩余页面
用户个人资料页面
- pages\profile\index.vue
设置页面
- pages\settings\index.vue
创建文章页面
- pages\editor\index.vue
文章详情页面
- pages\article\index.vue
处理顶部导航栏链接
- 用nuxt-link标签代替原来的a标签
处理导航链接高亮
-
nuxt匹配到路由后会给nuxt-link对应的导航链接添加一个类名,叫nuxt-link-active
-
当我们想要激活的导航默认添加的导航不是这个类名,我们可以修改这个默认值
-
但是我们发现home首页始终处于高亮状态
-
因为“是否激活”默认类名的依据是包含匹配。 举个例子,如果当前的路径是 /a 开头的,那么 也会被设置 CSS 类名。也就是导航栏链接是子,router-link中的匹配路由是父,只要nuxt-link中的字符串包含(父)地址栏中的完整路径字符串,就高亮,而nuxt-link中的/是包含/index或者/article等所有目录,按照这个规则,每个路由都会激活
-
解决办法
封装请求模块
npm i axios
- 创建axios实例,避免污染全局的axios
登录注册
实现基本登录功能
- 登录按钮
封装请求的方法
解析存储登录状态实现流程(jwt)
- 在客户端渲染的时候要拿到这个状态,在服务端渲染的时候也要拿到这个状态,即
前后端要共享数据状态
’
(1) 初始化容器数据
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default () => {
return new Vuex.Store({
state: {
user: null
},
mutations: {
// 保存用户信息
setUser (state, user) {
state.user = user
}
},
actions: {}
})
}
(2) 登录成功,将用户信息存入容器
this.$store.commit('setUser', data.user)
(3) 将登录状态持久化到 Cookie 中
安装 js-cookie
npm i js-cookie
const Cookie = process.client ? require('js-cookie') : undefined
Cookie.set('user', data.user)
(4) 从 Cookie 中获取并初始化用户登录状态
安装 cookieparser
npm i cookieparser
在store/index.js
actions: {
// 子服务端渲染的时候会自动调用nuxtServerInit方法,方法名固定
// 初始化容器以及需要传递给客户端的数据
// 这个特殊的action,只会在服务端渲染期间运行
nuxtServerInit ({ commit }, { req }) {
let user = null
// 如果请求头中有cookie,下面代码在服务端运行
if (req.headers.cookie) {
// 将请求头中的 Cookie 字符串解析为一个对象
const parsed = cookieparser.parse(req.headers.cookie)
try {
// 将 user 还原为 JavaScript 对象
user = JSON.parse(parsed.user)
} catch (err) {
// No valid cookie found
}
}
commit('setUser', user)
}
}
将登陆状态存储到容器中
nuxt中使用容器非常简单,它已经将Vuex集成到项目中了,只需要在根目录下创建store目录(必须叫store),nuxt发现store目录后会自动加载里面的容器模块(store/index.js)
- 在服务端渲染期间运行都是同一个实例,为了防止数据冲突,务必要把 state 定义成一个函数,返回数据对象
// 在服务端渲染期间运行都是同一个实例
// 为了防止数据冲突,务必要把 state 定义成一个函数,返回数据对象
export const state = () => {
return {
// 当前登录用户的登录状态
user: null
}
}
export const mutations = {
setUser (state, data) {
state.user = data
}
}
登录状态持久化
- 上面的状态仅仅存在内存中=>Vuex,当我们刷新的话,用户数据user初始化为null
- 为了防止页面刷新造成数据丢失,我们需要持久化,持久化就是我们之前分析的(jwt),不能把数据本地存储,因为本地存储只有客户端才能拿到,我们希望数据在客户端和服务端都能拿到,所以要将数据存储到cookie中,因为cookie中的数据,前后端都能拿
- 登录的代码(函数)执行是在客户端运行的
- 当点击登录的时候,我们已经把用户信息存储到cookie中了,当浏览器刷新的时候,cookie中的用户信息还在,刷新时,我们需要通过cookie中的数据初始化容器中的user状态
处理页面访问权限
- 导航栏的某些菜单,可以通过保存在Vuex的用户信息(是否存在)而显示与隐藏,但是虽然该菜单看不到了,但还是可以通过在地址栏直接输入地址链接而显示该菜单对应的页面
- 客户端渲染,可以使用Vue-router拦截器实现页面跳转之前的验证,这里不能使用该方式,因为我们要考虑到同构渲染的拦截,nuxt提供了路由中间件的拦截方式,既能处理客户端路由的拦截,也能出路服务端路由的拦截
中间件允许你定义一个自定义函数运行在一个页面或一组页面渲染之前
,返回一个promise对象每一个中间件应该放置在middleware/目录,文件名将成为中间件名称(middleware/auth.js将成为auth中间件)
一个中间件接收context作为一个参数
export default function (context) {
// Add the userAgent property to the context
context.userAgent = process.server
? context.req.headers['user-agent']
: navigator.userAgent
}
- 中间件的执行流程
创建中间件
- 在根目录下创建middleware目录,在middleware目录下新建authenticated.js文件,代表是否登录的中间件=>authenticated中间件,路由渲染之前自动调用
- 创建了中间件后,接下来就是谁来调用这个中间件,想要校验哪个页面,就把该中间件加给相应的页面,例如校验编辑页面editor页面,middleware属性
监听query参数改变
- 默认情况下,querty的改变不会调用asyncData方法,如果要监听这个行为,例如,在构建分页组件时,可以设置页面的watchQuery属性监听参数
http://loacalhost:3000/?page=1变成http://loacalhost:3000/?page=2时,page.vue组件的asyncDate函数不会被重现调用(页面重新渲染)
统一设置用户Token
-
在axios请求拦截器中统一设置token
-
那么上面的用户token从哪里取值呢?因为token的值是存储到cookie中的,而cookie中的值是存储到Vuex容器中的,但是我们可以直接在拦截器中取到store对象吗?答案是否定的
-
不同于纯客户端渲染方式,nuxt为了解决上面的问题,引入了
插件
的概念 -
Nuxt.js允许你在运行Vue.js应用程序之前执行js插件,这在你需要使用自己的库或者第三方模块时特别有用,需要注意的是,在任何vue组件的声明周期内,只有beforeCreate和create这两个方法会在客户端和服务端运行,其他生命周期函数仅在客户端被调用
-
定义插件
-
注册插件
-
在api中,导入的不在是request/index.js中的axios实例,而是plugins\request.js中的axios实例
文章发布时间格式化处理
- 处理日期的轻量级js库:dayjs,相对于momentjs更为轻量
- 在项目中有很多地方都要进行日期时间的格式化处理,建议将该方法封装成全局的过滤器,通过全局过滤器实现资源的最大化的重复利用
- 在nuxt中通过插件来定义全局的过滤器
设置页面meta优化SEO
- 因为title标签和meta标签有利于SEO,但是只有app.html中才有,当导航到具体的vue页面时,并不能取到title和meta信息,我们可以在methods中通过head方法来设置导航到该页面时整个html的title和meta信息
Nuxt.js发布部署
打包
- 在发布部署之前,应该把nuxt.js应用进行打包,然后将打包后的结果部署到生产服务器上
- 在package.json 中配置相关命令
- 执行命令 npm run build
- 执行命令 npm run start 开启服务器
最简单的部署方式
- 配置host+port
- 压缩发布包
- 把发布包传到服务器
- 解压(解压的文件是不包含第三方包的,因为压缩的发布包里面本来就没有第三方包,和客户端渲染不太一样)
- 安装依赖(下载第三方包)
- 启动服务
配置host+port
- host默认是localhost的,但localhost只是提供本机服务的(localhost无法提供对外访问),生产环境服务器如果想要对外提供访问,一定要设置为
0.0.0.0
,这样会监听所有的网卡地址,通过外网地址就能访问到(针对生产服务器
) 将.nuxt/static(静态资源)/nuxt.config.js/package.json/packag-local.json上传到服务器(因为要服务器上下载第三方包,所以要上传package.json文件)
执行打包
- 用本地的压缩工具对nuxt/static(静态资源)/nuxt.config.js/package.json/packag-local.json进行压缩,我们选在7.zip压缩工具进行压缩
- 得到realworld-nuxtjs.zip压缩包,我们的想办法将这个压缩包上传到服务器,
上传之前,我们得链接到服务端
,利用ssh工具链接远程服务器
ssh root@39.105.28.5
- ls命令,查看远程服务器下的文件
ls
- 在远程新建realworld-nuxt.js目录
mkdir realworld-nuxt.js
- 进入realworld-nuxt.js目录
cd realworld-nuxt.js
- 查看realworld-nuxt.js目录的路径
pwd // /root/realworld-nuxt.js
- 退出服务端
exit
- 利用scp命令将本地文件传输到服务器(scp是linux下的一个用来从本地和服务端传输文件的一个工具)
scp .\realworld-nuxt,js.zip root@39.105.28.5:/root/realworld-nuxt.js
- 重新连接远程服务器,进入到realworld-nuxt.js
ssh root@39.105.28.5
cd realworld-nuxt.js
- 查看该目录下所有文件(可以看到本地上传到服务器的压缩包)
ls
- 将压缩包解压
unzip realworld-nuxtjs.zip
- 查看解压后realworld-nuxt.js目录情况(发现没看到.nuxt目录,因为.开头的目录默认是隐藏目录,可以通过ls -a看到所有的目录,包括隐藏目录)
ls
- 安装地三方包
npm install
- 启动web服务
npm run start
使用PM2启动Node服务器
概念
- 上面当我们把项目部署到服务器后,使用npm run start命令启动了web服务,这个命令最终肯定执行的是nodejs相关的脚本把服务启动起来了,当我们通过这种方式
将服务启动起来后,现在这个命令是占用了命令行,也就是说退出这个命令行,这个web服务就会立即被关闭了,外界访问不到应用 - 有没有一种方式是后台来运行这个应用呢?接下来我们就要用到PM2工具了,PM2就是一个专门用来管理nodejs进程的一个应用,通过它可以把nodejs相关的一些应用运行在后台,保持运行状态
使用
- 安装(在web服务器的realworld-nuxtjs目录下安装,因为npm start命令是在服务端执行的)
npm install --global pm2
- 启动
pm2 start 脚本路径
- 之前是通过npm run start 来启动服务的,我们通过pm2来启动服务,可以使用npm start 后面跟上启动的脚本,如项目目录下有一个app.js,则pm2 start app.js,但我们在这里跟的不是一个脚本,而是一个npm 命令,我们可以下面命令来实现,后面的–start是在给npm传参(也就是说还是通过npm启动了这个服务,但是这个服务被pm2进行了管理)
pm2 start npm --start
- 关闭服务
pm2 stop 服务id
pm2常见命令
- pm2 list 查看应用列表
- pm2 start 启动应用
- pm2 stop 停止应用
- pm2 reload 重载应用
- pm2 restart 重启应用
- pm2 delete 删除应用(当一个应用不想被pm2管理了,可以把它从pm2中移除掉)
pm2 reload和pm2 restart对比
- pm2 reload:如果应用更新了,需要重启服务,reload的方式就是在保留一个进程激活的状态下,一个一个的去重启,这个reload的方式是最好的
- pm2 restart 也是重启应用,restart是把原有的进程全部关闭,然后开启一个新的进程,而reload是启动多个进程,把一个一个进程挨个关闭调,也就是说在启用起来新的实例之前,原有的实例会慢慢的关闭
自动化部署介绍
传统的部署方式
- 本地的代码更新了,生产服务器的代码没有同步跟新,需要本地执行构建,然后再要链接远程服务器,将本地代码上传到服务器,解压缩,安装依赖,重启服务,
每次本地代码更新后,都要重复上面的操作
,比较繁琐
现代化的部署方式(CI/CD)
- CI/CD
持续集成/持续部署
- 总共有四个平台(通过这四个平台 ,实现自动化的协作,完成自动部署)
- 用户本地
- Git远程仓库:这个远程仓库可以是gitHub,gerrit或者内网的gitLab都可以
- CI/CD服务:能持续集成/持续部署,能提供这样服务的东西,例如gitHub对应的gitAction;gitLab对应的gitCI,gerrit对应的Jenkins
- 生产环境web服务器
流程
- 用户本地有源代码,如果想要更新生产环境网站,我们肯定要在本地写代码,写好的代码通过git push推送到远程仓库,然后远程仓库收到推送以后,它只是用来存储代码的,并且将用户的推送通知给CI/CD服务,也就是原来在本地的操作,现在放到CI/CD平台上完成
- CI/CD服务介绍到远程仓库通知后, CI/CD将远程仓库中最新的代码下载到自己的服务当中,其中CI/CD服务中的
release(发布包)
指的就是上面所做的压缩发布包的环节,我们可以把发布包放到远程仓库中进行管理,这种方式不仅是把它备份存储起来,起到一个历史追溯的功能. - 将release发布包部署到web服务器
准备自动部署内容
使用GitHub Actions实现自动部署
CI/CD服务
- Jenkins
- Gitlab CI
GitHub Actions
小结:CI/CD服务的种类很多,但它们的作用都是一样的,都是用来做持续集成或者持续部署的,最终达成的目标都一样,在这里我们以GitHub Actions为例来演示
环境准备
- Linux服务器
- 把代码提交到GitHub远程仓库
配置GitHub Access Token
- 作用:在CI/CD中要使用到GitHub的用户身份令牌来访问操作远程仓库的权限(该流程稍微有点麻烦)
- 生成:
- 配置到项目的Secrets中: