需要说明的是,本文并非说Next的使用方式或者Egg的使用方式,建议阅读者对Egg和Next有一定了解。本文主要想表达的是对Next的一些吐槽,已经如何和Egg配合使用。
最近在思考着一个问题,前端从曾经的php或者java等后端通过模板引擎渲染页面到浏览器,到现在的react,vue,ng等mvc/mvvm框架,采用异步数据请求数据,客户端渲染页面。在我看来其实是一种进步。但是自从Node出来后,又搞了个SSR,或者说是服务器同构吧。感觉就是回到原点,只是换个语言而已。
我也知道SSR的意义在于有利于优化SEO,优化白屏速度,但是同时如果我们的网站对SEO有要求的话,那么就不得不使用SSR技术了。但是对于服务器的压力其实也会增加压力,所以使用SSR还是按需吧。个人的一点感觉,如果对SEO有很强的要求的话,感觉公司的规模有限,那么使用Node的小公司又有多少呢?例如淘宝京东等,与其做SEO还不如直接给钱搜索引擎供应商买排名来得更加直接。当然这是题外话,正所谓技多不压身,对于一个前端,使用Node慢慢变成了一个刚需,那当然要了解一下SSR的用法,以备不时之需。
对Next做了几个Demo之后,总结出了一些问题。
- Next对于React的构建,包切割等的webpack配置其实都做好了大量的配置,理论上其实我们不需要修改什么或者扩展什么,但是如果你的项目是旧项目而并非新项目,可能你自己的webpack也配置了一大堆配置,那么可能你就需要花些时间去兼容一下Next的webpack配置了,这里就有第一个问题,Next里面到底有什么配置,会和什么其他配置冲突呢?查阅文档后,貌似没有详细的说明。
- 文档中只说明就基本的使用方式,并没有说明API的使用方式,估计作者可能希望开发者只需要关注使用就可以,并不需要去较真原理以及API的使用方式。这真的好吗?
- 整个官网,并没有详细说明Next如何结合Express或者Koa的使用,一个项目也不可能就使用Next去替代Koa或者Express的作用吧?毕竟Next的定位应该是负责view渲染。
因为我是使用Egg的,既然也没有详细说明如何和Koa去配合使用,难道我还希望作者能告诉我怎么和Egg配合使用吗?那我就去问Egg的开发人员看看有没有好一点的例子。然后....得到的回复既然是!
呵呵!既然这样我就自己摸索一下咯!
查阅文档发现Next有去说道如何自定义启动一个Next,以下是官方例子:
代码语言:javascript复制// This file doesn't go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
可能我水平问题,青涩难懂。因为Egg的启动并不一样,Egg对启动做了封装。那么我就尝试一下兼容进去吧。查了一下Egg的文档,启动的一些生命周期,决定在beforeStart中实例化Next。
启动发现没有问题,那就尝试一下编写一个controller去渲染一个页面。
写一个路由
写一个controller
走你!
WTF!!!!
经过断点,发现render函数返回的是一个Promise,既然这样我就加一个await吧。
成功渲染,但是经过断点发现render返回的居然是undefined,那么它是如何渲染到页面的。直接告诉我render里面有东西!抱着求知的心我断点进去了一下代码,发现还真有东西!断点进去render函数发现最后是调用了一个sendHTML的函数,然后发现sendHTML函数帮我们做了一切响应的事情了!
以下是断点的代码,分别是next-server.js和render.js
next-server的render函数
render的sendHTML函数
附上github的源码地址:
next-serversendHTML
至此明白了Next为我们做的真多。顺带一提,Next使用了res.end()的方式返回数据,经过验证,Koa的洋葱模型的中间件触发正常。但是在使用Egg的时候,在没有用Next之前,我们需要对HTML模板做一些数据的提前注入,例如一些模板数据等,我们就用到egg-view这个插件,使用方式都是渲染出一个html字符串,放到ctx.body中进行返回的。如果需求我们渲染完html字符串,还需要做一些特殊处理的一些需求的时候,使用Next的render就并不合适了。
egg的模板渲染方式(只是一些demo)
通过阅读文档貌似没有发现返回html字符串的API,那么我们就继续看源码咯,在调用render的时候,发现一个惊喜。
一个叫renderToHTML的函数,经过断点,发现确实返回出来的是一个经过编译后的html字符串,这就满足了需求了!在看了一下,这个API并非内部API,而是暴露出来的,那就意味着我们能使用了。
github地址:
zeit/next.js
既然这样我们就修改成更像使用egg-view的方式吧!
验证通过!
其实Next的render内部也是调用renderToHTML,然后不返回出来,并且内部帮我们处理好返回的逻辑,添加上响应头的一些信息而已,所以理论上使用render和renderToHTML应该是没有任何区别的,都是能得到html字符串的。
还有一个问题不得不提的就是Next本来构建后会在_next文件夹下生成文件,通过页面依赖_next文件夹下的文件进行引入,所以必须要在Egg的路由中添加以下配置:
并且在对应的controller中使用handle函数
至此已经基本将Next接入到Egg中了。但是喜欢折腾的我,怎么会如此完事呢。这个handle是什么东西?
首先这个handle是通过在Egg启动Next并将其实例化后挂载在app中的。
那么这个是什么东西呢?我们将路由没有命中的全部指向了一个专门处理next生成文件的返回的controller中,然后我们并没有告诉这个handle函数任何需要返回的路径,只是单单的调用了一下,然后就实现了对应资源的返回了。我们又来断点看看源码。
首先我们在启动的时候调用了getRequestHandler函数,返回了一个handle函数。
然后我们在controller中调用了handle之后发生了什么事情呢?
当我们调用的时候,需要传入req和res到函数内,当然还有第三个参数,里面可以传入对应数据。之后内部经过一番格式化后,取到req的url值,然后传入了一个run函数内。
传入了run马上调用了一个router.match的方法,从名字上判断应该是通过Next内部自己的路由去匹配当前req的url然后返回对应的内容。我们都知道如果我们只是单纯的使用Next的情况下,它其实自己是有一个路由系统的,所有页面都是通过对应url然后在pages里面去找对应的页面,然后Next自己内部处理了_next开头的url到next文件夹中获取资源文件的。所以由此明白为何Next官方说道_next的路径和pages不要去修改的原因,就是因为内部做好了这一系列的配置导致的。(感觉扩展不好)
下面是router的源码:
router
由此我们基本明白了为何我们使用handle函数可以匹配到对应的我们需要的文件资源了。因为handle内部根据当前的req.url去匹配自身根据_next和pages文件对应的路径。通俗理解就是当Egg自身的路由都不命中的情况下,写一个匹配任何不命中路由的请求,然后调用handle去尝试匹配Next自己的路由配置看能否命中。
回头看Next官网的文档中的自定义启动的demo就完全明白它想表达的意思了。
至此,Egg接入Next基本完成,当然如果要细说的话,接入Egg后,怎么去监控Next的报错等等。但是现在还没有正式投入到生产中,日后投入生产后再进行后续的踩坑总结。
总结
- Next不得不吐槽就是文档了,只有基本的使用,并没有详细的API使用。
- 高度封装的原因,对于业务的多样性,能否兼容那么多不同的业务场景需要打一个问号。
- 对于服务器的压力会有多大的影响,貌似没有找到很好的数据支撑。
- 接入Egg不难,但是可能对于新项目使用比较友好,毕竟内置了webpack的一大堆配置,配合旧项目可能会出现比较头痛的情况。
到这里基本就爬出了个坑了,但是在各大网站都查不到Egg和Next的配合使用,不知道我自己这样用是否合适,会不会有什么问题,希望有大牛提一下建议!万分感谢!