next.js源码学习

编程入门 行业动态 更新时间:2024-10-24 18:19:42

next.js<a href=https://www.elefans.com/category/jswz/34/1770099.html style=源码学习"/>

next.js源码学习

本文为作者学习next.js框架架构的一些总结,仅出于个人观点,可留言共勉。
next版本:最新版
源码地址:.js

1. 项目入口

在我们使用next.js进行项目搭建时,package.json是必备的文件,其中scripts则为我们运行development、product等环境的入口,next相关的指令则包括

{"dev": "next dev","start":"next start","build": "next build","export": "next export",....
}

那么,这些next相关的指令是在哪里定义的呢?
。。。
经过观察源码发现next命令的定义为下:

// next.js\package.json
"next": "node packages/next/dist/bin/next"

接下来,可以再/packages/next中找到相关的定义:

// packages\next\bin\next.ts
...
const commands: { [command: string]: () => Promise<cliCommand> } = {build: async () => await import('../cli/next-build').then(i => i.nextBuild),start: async () => await import('../cli/next-start').then(i => i.nextStart),export: async () =>await import('../cli/next-export').then(i => i.nextExport),dev: async () => await import('../cli/next-dev').then(i => i.nextDev),telemetry: async () =>await import('../cli/next-telemetry').then(i => i.nextTelemetry),
}
...

哦。其实,这些build、start、dev、export等命令在调用时其实就是使用es6的async/await异步加载了相关的cli文件,从而完成项目的初始化、打包等。

接下来,就从常用的指令之一 ----next dev来探究下next的内部运行机制吧

2、next dev

packages\next\cli\next-dev.ts

import ... from ....;
...
const nextDev: cliCommand = argv => {const args = ...;const dir = ...;const port = args['--port'] || 3000;const appUrl = `http://${args['--hostname'] || 'localhost'}:${port}`;/* 配置全局的state变量(appUrl)*export function startedDevelopmentServer(appUrl: string) {*  consoleStore.setState({ appUrl })*}*/startedDevelopmentServer(appUrl);/** 本地使用node启动一个http服务* packages\next\server\lib\start-server.ts*/startServer(...).then().catch()
}
export { nextDev }

start-server.ts

import http from 'http' // ***此处调用的是node的http模块
import next from '../next'export default async function start (serverOptions: any,port?: number,hostname?: string
) {const app = next({...}); // ***注意这里,next是我们加载整个项目入口!!!const srv = http.createServer(app.getRequestHandler())await new Promise((resolve, reject) => {// This code catches EADDRINUSE error if the port is already in usesrv.on('error', reject)srv.on('listening', () => resolve())srv.listen(port, hostname)})// It's up to caller to run `app.prepare()`, so it can notify that the server// is listening before starting any intensive operations.return app
}

以上则是dev指令调用时的内部逻辑,
到此,我们可以发现,其实next.js就是在本地使用node开启了一个server,从而基于node实现服务端渲染的相关功能;

那么?问题来了,next的页面渲染和路由等机制怎么实现呢?
接下来,我们继续分析源码。。。

3. How to start a Render???

在 packages\next\server\next.ts 文件中,可以发现,next(…) 的内部实例化了一个自定义的Server对象

import Server, { ServerConstructor } from '../next-server/server/next-server'
...function createServer(options: NextServerConstructor): Server {....return new Server(options)
}
...
export default createServer

ok, 分析到这里,我们已经快要接近真相了,next的核心代码之一---- next-server

4. next-server

...
import { RenderOpts, RenderOptsPartial, renderToHTML } from './render'
...export default class Server {...public constructor({...}) {...// 注册路由,注意 generateRoutes方法的实现this.router = new Router(this.generateRoutes())// 此处对next的运行目录进行参数初始化initializeSprCache({...})// router模块的核心代码protected generateRoutes() {...// 通过对Router文件的解析可知catchAllRouteconst catchAllRoute:Route = {...,fn: () => {...// 此处的render方法即为挂载路由的实现;// render为当前Server的一个实例方法await this.render(req, res, pathname, query, parsedUrl)}}...}// 挂载render方法public async render(...) {.../* renderToHtml为render方法的核心部分,*此处会根据当前的pathname去动态的获取相对应的html资源*/const html = await this.renderToHTML(req, res, pathname, query).../* sendHTML将获取到的html文件返回给前端进行渲染 * packages\next\next-server\server\send-html.ts*/return this.sendHTML(req, res, html);}//renderToHTML 此处只关注最简实现public async renderToHTML() {.../*根据pathname去加载对应的Component * 在此方法内部核心实现为loadComponents: * packages\next\next-server\server\load-components.ts*/const result = await this.findPageComponents(pathname, query);try (result) {// 将result进行渲染return await this.renderToHTMLWithComponents(..., result, ...)}...}// private async renderToHTMLWithComponents(req: IncomingMessage,res: ServerResponse,pathname: string,{ components, query }: FindComponentsResult,opts: RenderOptsPartial) {...let html: string;if (isProduction){html = await getFallback(pathname)} else {if (isLikeServerless ) {// renderReqToHTMLhtml  = ...renderReqToHTML(...).html} else {// renderToHTMLhtml = renderToHTML(...)}}// node处理html文件,返回给浏览器渲染sendPayload(res, html, 'html');// 判断当前页面是否为首次渲染,是的话加缓存const {isOrigin,value: { html, pageData, sprRevalidate },} = await doRender();// Update the SPR cache if the head request and cacheableif (isOrigin && ssgCacheKey) {await setSprCache(ssgCacheKey, { html: html!, pageData }, sprRevalidate)}}}
}

以上就是next.js在运行时的大概逻辑
核心渲染部分总结如下:

  • generateRoutes(加载路由)
    • render
      • renderToHTML
        • findPageComponents
          • loadComponents
        • renderToHTMLWithComponents
          • renderReqToHTML/renderToHTML
          • sendPayload
          • (setSprCache)

以上仅为个人总结,有意见的欢迎提出改进

更多推荐

next.js源码学习

本文发布于:2024-03-10 17:50:29,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1728629.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:源码   js

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!