框架原理

  • 把Vue原本的dev开发模式改成Nuxt
  • 修改Vue的代码,让Vue的mount前的生命周期在服务器端完成
  • 这样改造后把本地的整个项目的代码包括node_modules一起移到服务器启动就行

迁移步骤

  • 按官网的步骤来就行
// npx命令安装了Node就自带了
npx create-nuxt-app <项目名>

// 选择NodeJS框架 Koa
// 选择UI框架
// 选择测试框架
// 完成


  • 配置文件
// nuxt.config.js
// 没有vue.config.js了

// 这个文件用来配置服务器的端口
// 渲染html页面的head标签数据,link,script,就是全局的css和js,都是用cdn地址
// 还有css和plugins,注意文件路径

module.exports = {
mode: 'universal',
server: {
port: 8000,
host: '127.0.0.1'
},
render: {
csp: true
},
// axios的请求前缀,跟上面的port一致
env: {
baseUrl: 'http://127.0.0.1:8000'
},
router: {
middleware: ['auth', 'i18n'],
extendRoutes (routes, resolve) {
routes.push({
path: '/',
redirect: {
name: 'timeline-title'
}
})
}
},
head: {
title: 'title',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script: []
},
css: [
'~/assets/css/reset.css',
'~/assets/css/page-transition.css',
'~/assets/scss/global.scss'
],
plugins: [
'~/plugins/axios.js',
'~/plugins/request.js',
'~/plugins/api.js',
'~/plugins/vue-global.js'
],
modules: [
'@nuxtjs/axios',
'@nuxtjs/style-resources',
'cookie-universal-nuxt'
],
axios: {
},
build: {
babel: {
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
],
},
extend (config, ctx) {
}
}
}


  • Nuxt的路由
// 把 <router-link to="/">首页</router-link> 改成
<nuxt-link to="/">首页</nuxt-link>
// 用$router跳转的就不用改了
// Nuxt是没有vue-router的
// 他的路由是用文件路径生成的,比如
pages/
--| _slug/
-----| 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'
}
]
}


  • Nuxt的app.vue
// 以前的app.vue是  <router-view />
// 现在是在layouts/xx.vue自定义的
// 默认有一个叫default.vue
// 内容显示在 <nuxt /> 里

// 如果文件里没有error.vue,需要自己写一个,就随便写一个,写上404就行
// 那指定哪个layout在哪配置呢,往下看


  • Vue文件的修改
// 在原来的Vue的属性和方法的基础上,添加了下面的熟悉和方法
// 这些方法是用在渲染的时候的

// asyncData,最重要的一个键, 支持 异步数据处理,另外该方法的第一个参数为当前页面组件的 上下文对象。
// head,配置当前页面的 Meta 标签, 详情参考 页面头部配置API。
// layout,指定当前页面使用的布局(layouts 根目录下的布局文件)。详情请参考 关于 布局 的文档。
// loading,如果设置为false,则阻止页面自动调用加载提示
// transition,指定页面切换的过渡动效, 详情请参考 页面过渡动效。
// scrollToTop,布尔值,默认: false。 用于判定渲染页面前是否需要将当前页面滚动至顶部。这个配置用于 嵌套路由的应用场景。
// validate,校验方法用于校验 动态路由的参数。
// middleware,指定页面的中间件,中间件会在页面渲染之前被调用, 请参考 路由中间件

// 放一个例子
// 这个方法需要主动调用,等于是自定义生命周期函数,而且必须是这个名字
// 这个方法代替了vue的 beforeCreate 和 created 两个生命周期
// mounted 还是在客户端执行,mounted不应该写数据请求,防止重复请求
// 结构参数,app是this实例,params是请求的url,store是vuex实例
async asyncData({ app, params, store }) {
// 可以在asyncData获取和修改store
// 往下看可以发现这个categoryList的数据是来自validate的
let categoryList = store.state.category.recommendCategoryList
categoryList[0].name = "text";
store.commit('category/UPDATE_TIMELINE_CATEGORY_LIST', categoryList)

// 作者榜单
let res = await app.$api.getAuthorRank({
channel: params.name,
after: '',
first: 20
}).then(res => res.s === 1 ? res.d : {})
return {
categoryList,
authors: res.edges,
pageInfo: res.pageInfo
}
},
head() {
return {
title: this.pageInfo.name || 'xxx'
}
},
async validate ({ app, params, store }) {
// validate是执行在asyncData之前的
// 可以在validate获取和修改store
if (params.id && params.id != 'undefined') {
let categoryList = []
// 获取分类列表缓存
if (store.state.category.timelineCategoryList.length) {
categoryList = store.state.category.timelineCategoryList
} else {
categoryList = await app.$api.getCategories().then(res => res.s === 1 ? initCategoryList.concat(res.d.categoryList) : initCategoryList)
store.commit('category/UPDATE_TIMELINE_CATEGORY_LIST', categoryList)
}
return true
}
return false
},
// 下面就是原Vue的老代码
data() {
return {
pinDetail: {},
page: 1,
comments: []
}
},
...


  • 上面我们使用了vuex
// 但是为什么在nuxt.config.js的plugins里没有看到引入
// 因为nuxt自带了,只要项目里有个store的文件夹
// 放入js就行,js里是函数型的state 和 同步的mutations

// text.js
export const state = () => ({
isTopbarBlock: true, // 顶部栏是否显示
})

export const mutations = {
UPDATE_TOPBAR_BLOCK(state, payload){
state.isTopbarBlock = payload
}
}

// 在page的validate里用commit执行
store.commit('文件名/UPDATE_TOPBAR_BLOCK', 参数)


  • 请求三人组
// Nuxt不能用正常的axios,一定要用@nuxtjs/axios,他默认让vue.$axios = axios
// 还有cookie-universal-nuxt插件,这个插件默认让vue.$cookie = 请求用户的cookie

// plugins/axios.js,对axios进行配置
export default function ({ app: { $axios, $cookies } }) {
$axios.defaults.baseURL = process.env.baseUrl
$axios.defaults.timeout = 30000
$axios.interceptors.request.use(config => {
config.headers['X-Token'] = $cookies.get('token') || ''
config.headers['X-Device-Id'] = $cookies.get('clientId') || ''
config.headers['X-Uid'] = $cookies.get('userId') || ''
return config
})
$axios.interceptors.response.use(response => {
if (/^[4|5]/.test(response.status)) {
return Promise.reject(response.statusText)
}
return response.data
})
}

// plugins/request.js
export default ({ app: { $axios } }, inject) => {
let requestList = {}
let methods = ['get', 'post', 'put', 'delete']
methods.forEach(method => {
let dataKey = method === 'get' ? 'params' : 'data'
requestList[method] = function(url, data) {
return $axios({
method,
url,
[dataKey]: data
}).catch(err => {
console.error(err)
return {
s: 0,
d: {},
errors: [err]
}
})
}
})
inject('request', requestList)
}

// plugins/api.js
export default ({ app: { $request } }, inject) => {
inject('api', {
/**
* 登录验证
* @param {string} password - 密码
* @param {string} phoneNumber - 手机号码
*/
loginAuth(data) {
return $request.post('/v1/auth/login', data)
},
/**
* 身份验证
*/
isAuth() {
return $request.get('/v1/auth/authentication')
}
...
})
}


  • Vue全局设置
// plugin/vue.global.js

// 在这里配置一些组件和过滤器
import Vue from 'vue'
import xxx from '~/components/xxx'
import utils from '~/utils/utils'

Vue.use(xxx)

Vue.filter('formatTime', d => utils.formatTime(d))

export default function (context, inject) {
inject('utils', utils)
}


  • 服务器搭建,这里用Koa
// server/index.js

const fs = require('fs')
const Koa = require('koa')
const cors = require('koa2-cors')
const helmet = require('koa-helmet')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = new Koa()
const router = new Router()

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

function useMiddleware(){
app.use(helmet())
app.use(bodyParser())
//设置全局返回头
app.use(cors({
origin: function(ctx) {
return 'http://localhost:8000'; //cors
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 86400,
credentials: true, // 允许携带头部验证信息
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Token', 'X-Device-Id', 'X-Uid'],
}))
}

function useRouter(path){
// 路由就是接口,这里怎么配置就不写了
}

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

const {
host = process.env.HOST || '127.0.0.1',
port = process.env.PORT || 3000
} = nuxt.options.server

await nuxt.ready()
// Build in development
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
useMiddleware()
useRouter()
app.use((ctx) => {
ctx.status = 200
ctx.respond = false // Bypass Koa's built-in response handling
ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
nuxt.render(ctx.req, ctx.res)
})
app.listen(port, host)
}

start()


  • 到这里就大致都改好了,看看Nuxt的命令有哪些
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon --inspect server/index.js --watch server",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
},


  • 执行​​npm run dev​​,会打包出一个​​.nuxt​​文件,顺便打开服务器,直接访问就行,如果要放到服务器,就把所有的代码移到服务器,同样的方法启动就行

研究一下.nuxt文件

// router.js,这个就是上面说到的自动生成的路由文件
// App.js,就是读取layouts文件夹生成出来的模板文件

// index.js,这个非常值得研究
// index.js把nuxt.config.js里的配置读取后生成出来的
// 最开始可以看到很多的plugins
// 然后引入可很多的component
// 还有一个inject方法,这个方法在上面的请求三人组里可以看到很多次,就是在这里调用的,是一个固定写法,用来给Vue实例注册$xx属性的,看代码

const inject = function (key, value) {
if (!key) {
throw new Error('inject(key, value) has no key provided')
}
if (value === undefined) {
throw new Error(`inject('${key}', value) has no value provided`)
}
key = '$' + key
// Add into app
app[key] = value
}

// server.js,这个就是服务器渲染的核心
// 在这里把请求的路径解析后,知道请求的是哪个页面的路由,然后读取文件,看代码
export default async (ssrContext) => {

ssrContext.redirected = false
ssrContext.next = createNext(ssrContext)
ssrContext.beforeRenderFns = []
ssrContext.nuxt = { layout: 'default', data: [], fetch: [], error: null, state: null, serverRendered: true, routePath: '' }

const { app, router, store } = await createApp(ssrContext)
const _app = new Vue(app)

ssrContext.nuxt.routePath = app.context.route.path
ssrContext.meta = _app.$meta()
ssrContext.asyncData = {}

const beforeRender = async () => {
await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
ssrContext.rendered = () => {
ssrContext.nuxt.state = store.state
}
}

/*
** Set layout
*/
let layout = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layout === 'function') {
layout = layout(app.context)
}
await _app.loadLayout(layout)
if (ssrContext.nuxt.error) {
return renderErrorPage()
}
layout = _app.setLayout(layout)
ssrContext.nuxt.layout = _app.layoutName

// asyncDatas非常的核心
const asyncDatas = await Promise.all(Components.map((Component) => {
const promises = []

if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
const promise = promisify(Component.options.asyncData, app.context)
promise.then((asyncDataResult) => {
ssrContext.asyncData[Component.cid] = asyncDataResult
applyAsyncData(Component)
return asyncDataResult
})
promises.push(promise)
} else {
promises.push(null)
}

// Call fetch(context)
if (Component.options.fetch && Component.options.fetch.length) {
promises.push(Component.options.fetch(app.context))
} else {
promises.push(null)
}

return Promise.all(promises)
}))

await beforeRender()

return _app
}


如果有必须使用的js,但不需要在服务端使用的js

  • 比如mavon-editor
// nuxt.config.js 中添加plugins配置
plugins: [
...
{ src: '@/plugins/vue-mavon-editor', ssr: false }
],


// 在页面或者组件中引入套上<no-ssr>
<template>
<div class="mavonEditor">
<no-ssr>
<mavon-editor :toolbars="markdownOption" v-model="handbook"/>
</no-ssr>
</div>
</template>


我还是不会用怎么办

把上面的github下载下来,没用的代码删了,改成自己的就行