Vue2+koa2+nuxt+ssr项目初构

在当下前端组件化特别流行的时代,怎样保证团队进行Vue组件化开发,又能保证网站SEO呢?

下面我就讲一下我的经历~

开始我一直以为Vue还没有完善的SSR脚手架,直到自己写了一个比较粗糙的跟同事们普及的时候,一同事说出了nuxt这个词的时候。

唉,自己竟然不知道,不要笑话我…真实无知了

现在开始讲一下我的Vue nuxt 项目初构的过程

环境搭建

nuxt 相关的脚手架已经集成到了 vue-cli,同时提供 starter、express、koa、adonuxt

这里我们用的是 koa2

1
2
3
4
vue init nuxt/koa <project-name>
cd <project-name> # move to your project
npm install # or yarn install*[see note below]
npm run dev

此时监听 3000 端口,如果有 bug,别犹豫,先升级 node 版本到最新。

项目跑起来之后,有一个简单的轮廓,两个页面,index 和 about。

目录结构

具体请参考:https://zh.nuxtjs.org/guide/directory-structure

但是这个根目录结构对于我这个有特殊癖好的人来说感觉太太太… 特么的多了

下面进行改造

1. 在nuxt.config.js中添加节点

1
srcDir: 'client/', //该配置项用于配置应用的源码目录路径。

2. 在项目根目录创建client目录,并将assets、components、layouts、middleware、pages、static、store移到该目录

以上对于client端的改造告以段落

Koa2 server端改造

1. 改造server端,必须先加上路由,加路由就得加自动路由,没那功夫建个子路由还要到server/index.js里进行添加

创建server/autoRoutes.js,大致意思自己意会吧,相信大家都能看懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Created by wangyipeng on 2017/9/26.
*/
const router = require('koa-router')()
const path = require('path')
const fs = require('fs')
var exports = {}
exports['auto'] = function (app) {
let files = fs.readdirSync(path.join(__dirname, 'controllers'))
let jsFiles = files.filter((f) => {
return f.endsWith('.js')
}, files)
// 控制器文件
for (let f of jsFiles) {
console.log(`import controller from file ${f}...`)
let name = f.substring(0, f.length - 3)
exports[name] = require('./controllers/' + f)
router.use('/' + name, exports[name].routes(), exports[name].allowedMethods())
app.use(exports[name].routes(), exports[name].allowedMethods())
}
}
module.exports = exports

在server/index.js里添加

1
2
3
import AutoRoutes from './autoRoutes' //这个放在最前边,不多解释了
AutoRoutes.auto(app) //这个放在原有的app.use前边就行了

创建在server/controllers文件夹

写一个测试控制器server/controllers/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Created by wangyipeng on 2017/9/27.
*/
const router = require('koa-router')()
router.get('/index', async function (ctx, next) {
ctx.body = [
{
name:'yipeng', age: '29'
},
{
name:'yihang', age: '18'
}
]
})
module.exports = router

大家可以npm run dev 运行下试试

浏览器输入http://localhost:3000/user/index查看效果

2. 没有日志的东西是没法在线上运行的,项目稳定不稳定都不知道,查问题都不知道

下面安装koa-log4

1
npm install koa-log4 --save

新建配置文件config/log4js.js

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
export default {
'appenders': [
{
'type': 'console'
},
{
'type': 'clustered',
'appenders': [
{
'type': 'dateFile',
'filename': 'http.log',
'pattern': '-yyyy-MM-dd',
'category': 'http'
},
{
'type': 'file',
'filename': 'app.log',
'maxLogSize': 10485760,
'pattern': '-yyyy-MM-dd',
'numBackups': 5
},
{
'type': 'logLevelFilter',
'level': 'ERROR',
'appender': {
'type': 'file',
'filename': 'errors.log'
}
}
]
}
]
}

接下来还是在server/index.js里添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Log4js from 'koa-log4'
import logConfig from '../config/log4js' //这两行放在最上边,不解释
// 生成logs目录 && 加载配置文件 start 这段放在中间位置即可
const logger = Log4js.getLogger('app')
const logPath = path.join(__dirname, 'logs')
try {
require('fs').mkdirSync(logPath)
} catch (err) {
if (err.code !== 'EEXIST') {
console.error('Could not set up log directory, error was: ', err)
process.exit(1)
}
}
Log4js.configure(logConfig, {cwd: logPath})
// 生成logs目录 && 加载配置文件 end
//我这里把开发过程的日志去掉了,太烦躁
if (!config.dev) {
app.use(Log4js.koaLogger(Log4js.getLogger('http'), {level: 'auto'}))
}

前端报错收集

想要收集项目运行过程中用户的前端报错,就得依赖window.onerror了

1.在client文件夹创建app.html并添加window.onerror方法

为什么呢,因为脚手架默认html文件你是找不到的,要想修改必须新建一个app.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
<script>
window.onerror = function (errorMessage, scriptURI, lineNumber, columnNumber, errorObj) {
var requestMessage = location.href + ' '
+ errorMessage + ' '
+ scriptURI + ' '
+ lineNumber + ' '
+ columnNumber + ' '
+ JSON.stringify(errorObj) + ' '
+ navigator.userAgent;
new Image().src="/error/reporter?err=" + encodeURIComponent(requestMessage);
}
</script>
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>

2.上边这里接口是我项目内写的

我新建了一个server/controllers/error.js

1
2
3
4
5
6
7
8
9
10
11
/**
* Created by wangyipeng on 2017/9/27.
*/
const router = require('koa-router')()
router.get('/reporter', async function (ctx, next) {
let logger = ctx.Log4js.getLogger('reporter')
logger.error(ctx.query.err)
ctx.body = {result: 'ok'}
})
module.exports = router

具体ctx.Log4js是怎么来的?我是在server/index.js中添加以下代码:

注意:放在AutoRoutes.auto(app)前边

1
2
3
4
app.use(async (ctx, next) => {
ctx.Log4js = Log4js //给koa2上下文添加Log4js
await next()
})

辅助方法

新建配置文件config/app.js

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
apiPath: {
local: {
host: 'http://localhost:3000',
getUserList: {
method: 'get',
url: '/user/list'
}
}
}
}

在根目录创建helper目录

新建helper/utils.js

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
/**
* 辅助函数
* @author wangyipeng
*/
export default {
isFunction (fn) {
return Object.prototype.toString.call(fn) === '[object Function]'
},
/**
*@param {Object}|{Array} object 需要遍历处理的对象或数组
*@param {Function} callback 遍历处理回调函数
*@param {Array} args callback回调函数的附加参数
*/
each (object, callback, args) {
let name
let i = 0
let length = object.length
let isObj = length === undefined || this.isFunction(object)
if (args) {
if (isObj) {
for (name in object) {
if (callback.apply(object[name], args) === false) {
break
}
}
} else {
for (; i < length;) {
if (callback.apply(object[i++], args) === false) {
break
}
}
}
} else {
if (isObj) {
for (name in object) {
if (callback.call(object[name], name, object[name]) === false) {
break
}
}
} else {
for (let value = object[0]; i < length && callback.call(value, i, value) !== false; value = object[++i]) {
}
}
}
return object
}
}

新建helper/services.js,这个挺有意思的通过配置文件一下就生成了所有接口的请求方法,再也不用一个写一遍了

这个依赖了axios,自行npm安装吧

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
import {apiPath} from '../config/app'
import axios from 'axios'
import utils from './utils'
/**
* 构造接口请求方法
* @author wangyipeng
*/
let services = {}
for (let i in apiPath) {
let hostApiPath = apiPath[i]
let apiHost = hostApiPath['host']
services[i] = {}
for (let ind in hostApiPath) {
if (ind === 'host') {
continue
}
let api = hostApiPath[ind]
services[i][ind] = async function (params, isNeedStatus = false) {
let apiUrl = api.url
let newParams = {}
if (params) {
utils.each(params, function (ind, param) {
if (apiUrl.indexOf('{' + ind + '}') > -1) {
apiUrl = apiUrl.replace('{' + ind + '}', param)
} else {
newParams[ind] = param
}
})
}
let data = newParams
let config = {}
let response = {}
if (api.method === 'put' || api.method === 'post' || api.method === 'patch') {
response = await axios[api.method](apiHost + apiUrl, data, config)
if (!isNeedStatus) {
response = response.data
}
} else {
config.params = newParams
response = (await axios[api.method](apiHost + apiUrl, config))
if (!isNeedStatus) {
response = response.data
}
}
return response
}
}
}
export default services

想要调用http://localhost:3000/user/list接口直接如下:

1
2
3
import services from 'helper/services.js' //具体目录是啥看你项目用的地方吧
let response = await services.local.getUserList() //想要传参 {key:value}形式传参

以上,所有的初期工作就基本完成了,基本可以交给团队进行项目开发了

源码地址:https://github.com/1peng/vue2_koa2_nuxt_ssr