本篇会介绍如何将现有Vue项目,迁移到Nuxt框架下。

公司前阵子为了给现有项目做seo,同时对缓存以及pwa、添加一些服务端功能等功能需求进行一定的优化和管理,于是决定将一个现有的纯vue项目,迁移到nuxt框架下。本篇重点写的是是在当时做迁移的时候记录的一些坑,而具体的一些步骤其实网上都有很详细的描述,我就不再做论述。

学习本篇前,你必须具备以下知识储备:

  • 熟悉Vue项目。
  • 对Nuxt项目有一定的初步了解。
  • 有自己查阅文档的能力。

对于项目的基础迁移步骤,可以参考这篇文章,里面都有非常详细的记载,但也是仅供参考,因为不同的项目也是有一定的区别。

翻阅了一下之前在做迁移的时候所记录的文档,总结下几个大坑,如果里面有你所遇到的,希望能帮到你。

  • 如何将vue-router适配到nuxt的路由管理中?
  • 如何适配并封装axios?
  • 如何将vue.config.js适配到nuxt.config.js中?
  • 全局型plugins该如何引入?

你必须先知道的一些坑

在迁移的时候,有遇到一些大坑,翻阅文档,结合stackoverflow才摸透。因为国内环境中使用nuxt的人并不多,生态还不是很完善,所以踩到一些坑的时候并不是能很快排查解决。

这里先提出几个你必须要知道的几个坑,后面的文章里也是默认你已经熟知这几点。

其实我感觉整篇文章最精华的就是这里了

什么是nuxt

对于nuxt,官方是这么介绍的:

Nuxt.js 是一个基于 Vue.js 的通用应用框架

他集成了vue,vue-router,vuex等等一些较为基础和重要的组件/框架。

(也是因为集成的原因,所以无法从cdn引入vue,因为框架内已集成,再从cdn引入会重复打包,目前还没有找到相关解决办法)

spa与ssr

关于什么是spa与ssr,网上的资料已经非常全面了。这里说一下会踩到的坑。在ssr模式下,nuxt会先进行服务端的解析,再进行客户端的解析。于是乎在运行的时候,就会触发一些变量、组件等undefined的情况。

这里以最典型的window对象为例,在项目中如果要使用window对象,并且还是在客户端解析前(如created生命周期内),就会触发undefind的问题。

解决办法是,更换为在服务端解析后的生命周期(如mouted生命周期)内执行,或者是加入以下代码进行判断:

1
2
3
if (process.browser) {
........
}

在迁移的过程中你会遇到非常非常多次这个问题,而解决办法就是如此的简单,仅需一行代码。

万物都是plugins

在nuxt下,许许多多的组件以及配置,都是通过plugins的形式完成配置。这里也会涉及到axios、localstorage等的封装。

而在plugins进行引入的时候,也要注意下是否开启ssr。

箭头函数

因为axios在nuxt中进行封装的时候使用了箭头函数,在对接过程中偶然发现了在watcher函数中是不允许使用箭头函数的。于是对于想在watcher请求中发起请求需要换一种写法,vue官方文档中是这么说的:

vue文档


将vue-router迁移到nuxt的路由管理中

在nuxt与vue中,有一个最为明显的区别就是nuxt采用的是根据pages页面的路径自动生成所对应的路由。所以在项目的迁移中,这里也是花费时间最多的模块。

迁移方案有两种:

  1. 沿用旧的vue-router结构,使用官方提供的的@nuxtjs/router插件,以读取沿用旧的router.js文件配置。同时修改srcDir路径(这个配置在文章开头推荐文章中已经有很详细的记录了)。
  2. 一点一点迁移到pages文件夹中,进行适配。

nuxtjs/router

先来大致说一下第一种思路,即使用nuxt官方插件的形式。

首先要理解下nuxt的页面渲染过程,官方文档中是这么说的:

Nuxt在做渲染的时候包裹了很多层。首先有一个Document作为其模板,然后再去寻找其布局的页面,找到对应的页面之后,再根据引用去找到相关的组件进行渲染,数据请求与数据挂载,一系列完成之后,把剩余的路由信息返还给客户端,渲染完成,这个就是Nuxt简单的渲染流程。

首先最开始的进行安装@nuxtjs/router插件:

1
npm install --save @nuxtjs/router

然后在nuxt.config.js下写入modules。需要注意,写入buildModules的话只会生效于dev环境下。若是直接将vue项目的src目录移动过来的话,还需要在配置文件中配置srcDir以更改路径。之后@别名或是默认目录下将会从src目录开始。

1
2
3
4
5
6
7
8
9
10
module.exports = {
srcDir: 'src/',
modules: [
// Simple usage
'@nuxtjs/router',

// With options
['@nuxtjs/router', { /* module options */ }]
],
}

其中options中可以配置path(默认为srcDir),fileName(默认为router.js),以及keepDefaultRouter(保持启用普通nuxt路由的选项)。

而router.js相对于原vue中的router,需要进行一些改写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mport Vue from 'vue'
import Router from 'vue-router'

import MyPage from '~/components/my-page'

Vue.use(Router)

export function createRouter() {
return new Router({
mode: 'history',
routes: [
{
path: '/',
component: MyPage
}
]
})
}

同时需要注意,若是以下异步调用的写法:

1
component: () => import('@/views/systemIntroduction/SystemIntroduction.vue'),

会报以下错误:

render function or template not defined in component: anonymous

需要将其改为:

1
import ForgetPwd from "./views/user/ForgetPwd";

同时完成其他的一些适配。

在页面并不是太多的情况下,我推荐第二种。一是长痛不如短痛,二是涉及到router-name不适配的为题,三是也可以趁机优化下页面代码。而对于迁移到pages下的方法,不再做过多论述,参考官方文档进行迁移即可。


axios的封装

对于发起请求,nuxt官方是有封装了自己的组件的。而在我vue的原项目中,也是有的具体的封装方法。最后选择了这种封装方法,跟原项目较为接近。

还有另一种较为正式的写法,将axios封装为pluhins的形式。

首先安装@nuxtjs/axios,然后在plugins中这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const http = app => {
const axios = app.$axios
const store = app.store
const router = app.route
let errTime = 0
let noLogin = false
axios.defaults.timeout = 40000

axios.interceptors.request.use
(
config => {...},
error => {...}
)
...

然后在nuxt.config.js中进行plugins设置,并将该组件的ssr设置为false。

于是你就可以在使用的axios请求就已经被进行封装过,具体使用方法为:

1
this.$axios.post('url',params).then(data => {})

nuxt.config.js中具体适配(build)

nuxt是集合了 Webpackvue-loaderbabel-loader 来进行打包处理的。所以一些在vue中对于webpack的配置需要更换一下写法。

以我项目中为例,参考其中的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
build: {
transpile: [ /vant.*?less/ ],
// css 独立打包 link 的形式加载
cssSourceMap: false, // false-生产模式,
loaders: {
// 对应vue中的loaderOptions
},
less: {
modifyVars: {
green: '#7966FE'
}
},
minimizer: [
// webpack4 使用的压缩插件,用来替代webpack3的 UglifyJsPlugin
new TerserPlugin({
terserOptions: {
warnings: false,
compress: {
drop_console: true, // 可选:false,生产移除 console.log
pure_funcs: ['console.log']
},
output: {
comments: false // 是否保留代码注释
},
cache: true,
parallel: true,
// Must be set to true if using source-maps in production
sourceMap: process.env.NODE_ENV !== 'production'
}
})
],
// 下面这种写法是重点,如何在build中配置plugins
plugins: [
// 以CompressionPlugin为例子:
new CompressionPlugin({
// CompressionPlugin 代码压缩工具
filename: '[path].gz[query]', //目标资源名称。[file] 会被替换成原资源。[path] 会被替换成原资源路径,[query] 替换成原查询字符串 原asset关键字已被改为filename
algorithm: 'gzip', //算法
test: new RegExp(
'\\.(js|css|svg)$' //压缩 js 与 css
),
threshold: 10240, //只处理比这个值大的资源。按字节计算
minRatio: 0.8, //只有压缩率比这个值小的资源才会被处理
cache: true
})
],
/*
** You can extend webpack config here
*/
extend(config, ctx) {
// Run ESLint on save
if (ctx.dev && ctx.isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
// 对应chainWebpack 对webpack进行深层样式修改
const svgRule = config.module.rules.find(rule => rule.test.test('.svg'))
svgRule.exclude = [resolve('assets/icons/svg')]
// Includes /assets/svg for svg-sprite-loader
config.module.rules.push({
test: /\.svg$/,
include: [resolve('assets/icons/svg')],
loader: 'svg-sprite-loader',
options: {
symbolId: 'icon-[name]'
}
})
// user svgo-loader
config.module.rules.push({
test: /\.svg$/,
include: [resolve('assets/icons/svg')],
loader: 'svgo-loader'
})
}

这里不做过多论述,只是参考webpack等build中代码书写格式,进行配置即可。


全局型plugins在nuxt中的引入

起因是这样的,业务需求我需要把我在vue中使用的一个全局插件迁移到nuxt中。

在原项目中,代码如下:

1
2
3
4
5
6
7
8
const nativeShare = new nativeShare()
const nativeShare = new NativeShare({
// 让你修改的分享的文案同步到标签里,比如title文案会同步到<title>标签中
// 这样可以让一些不支持分享的浏览器也能修改部分文案,默认都不会同步
syncDescToTag: false,
syncIconToTag: false,
syncTitleToTag: false
})

很显然迁移过去nuxt之后会报undefined nativeShare的问题。

解决办法是将nativeShare注册成一个可以全局使用的plugins。

一顿骚操作后,发生了这个问题。在github查看其他人的代码写法后,发现了一个可以引用生效的写法,plugins的具体代码如下:

1
2
3
4
import NativeShare from 'nativeshare'
import Vue from 'vue'

Vue.prototype.$nativeShare = new NativeShare()

并且这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
this.$nativeShare.setConfig({
// 让你修改的分享的文案同步到标签里,比如title文案会同步到<title>标签中
// 这样可以让一些不支持分享的浏览器也能修改部分文案,默认都不会同步
syncDescToTag: false,
syncIconToTag: false,
syncTitleToTag: false,
wechatConfig: {
appId: '',
timestamp: '',
nonceStr: '',
signature: ''
}
})

其他大小坑

这一部分我也觉得算是文章的意义hhh,筛选了一些当时做迁移的时候所遇到的大小坑,如果你也遇到了,希望可以帮到你。

如果有遇到任何问题,或者是文章中有任何纰漏,欢迎联系我,推荐使用邮箱。万分感谢。

评论