3

浅析EggJS接入NextJS

 3 years ago
source link: https://zhuanlan.zhihu.com/p/54841918
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

浅析EggJS接入NextJS

腾讯 高级前端工程师

个人订阅号,知乎和微信同步推文,希望大家关注一波!

微信订阅号:小前端看世界,id:fe_watch_world

v2-23f32e2ea19e9b6401c96d4c5c1356f7_720w.jpg

需要说明的是,本文并非说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之后,总结出了一些问题。

  1. Next对于React的构建,包切割等的webpack配置其实都做好了大量的配置,理论上其实我们不需要修改什么或者扩展什么,但是如果你的项目是旧项目而并非新项目,可能你自己的webpack也配置了一大堆配置,那么可能你就需要花些时间去兼容一下Next的webpack配置了,这里就有第一个问题,Next里面到底有什么配置,会和什么其他配置冲突呢?查阅文档后,貌似没有详细的说明。
  2. 文档中只说明就基本的使用方式,并没有说明API的使用方式,估计作者可能希望开发者只需要关注使用就可以,并不需要去较真原理以及API的使用方式。这真的好吗?
  3. 整个官网,并没有详细说明Next如何结合Express或者Koa的使用,一个项目也不可能就使用Next去替代Koa或者Express的作用吧?毕竟Next的定位应该是负责view渲染。

因为我是使用Egg的,既然也没有详细说明如何和Koa去配合使用,难道我还希望作者能告诉我怎么和Egg配合使用吗?那我就去问Egg的开发人员看看有没有好一点的例子。然后....得到的回复既然是!

呵呵!既然这样我就自己摸索一下咯!

查阅文档发现Next有去说道如何自定义启动一个Next,以下是官方例子:

// 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-server​github.comsendHTML​github.com

至此明白了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地址:

既然这样我们就修改成更像使用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​github.com

由此我们基本明白了为何我们使用handle函数可以匹配到对应的我们需要的文件资源了。因为handle内部根据当前的req.url去匹配自身根据_next和pages文件对应的路径。通俗理解就是当Egg自身的路由都不命中的情况下,写一个匹配任何不命中路由的请求,然后调用handle去尝试匹配Next自己的路由配置看能否命中。

回头看Next官网的文档中的自定义启动的demo就完全明白它想表达的意思了。

至此,Egg接入Next基本完成,当然如果要细说的话,接入Egg后,怎么去监控Next的报错等等。但是现在还没有正式投入到生产中,日后投入生产后再进行后续的踩坑总结。

  • Next不得不吐槽就是文档了,只有基本的使用,并没有详细的API使用。
  • 高度封装的原因,对于业务的多样性,能否兼容那么多不同的业务场景需要打一个问号。
  • 对于服务器的压力会有多大的影响,貌似没有找到很好的数据支撑。
  • 接入Egg不难,但是可能对于新项目使用比较友好,毕竟内置了webpack的一大堆配置,配合旧项目可能会出现比较头痛的情况。

到这里基本就爬出了个坑了,但是在各大网站都查不到Egg和Next的配合使用,不知道我自己这样用是否合适,会不会有什么问题,希望有大牛提一下建议!万分感谢!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK