良久前写了对于 getStaticPropsgetStaticPaths 的内容,然而半年过来了源码解析就始终遗记了,不久前有人揭示才想起来,补下坑。

本文次要是解读下 getStaticPropsgetStaticPaths 相干的源码,不理解这两个 API 的倡议先看下之前的文章再看。

getStaticProps

首先 getStaticProps 是利用于 SSG 场景,咱们先看下 packages/next/server/render.tsx 中相干的代码:

const isSSG = !!getStaticProps;const pageIsDynamic = isDynamicRoute(pathname);if (isSSG && !isFallback) {    let data: UnwrapPromise<ReturnType<GetStaticProps>>;    try {        data = await getStaticProps!({            ...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined),            ...(isPreview ? { preview: true, previewData: previewData } : undefined),            locales: renderOpts.locales,            locale: renderOpts.locale,            defaultLocale: renderOpts.defaultLocale        });    } catch (staticPropsError: any) {        // ....    }    // ...}

isFallback 能够先不论。能够看到 getStaticProps 同样能够为异步函数,而是否为 SSG 就是由是否存在 getStaticProps 函数来决定的,SSG 场景下的 pageIsDynamic 则必须配合 getStaticPaths 应用,能够看到 getStaticProps 会接管几个参数:

  • params 是在动静页面的路由参数
  • previewDatapreview: preview 模式的相干数据
  • locales, localedefaultLocale 多语言相干参数

执行实现后 getStaticProps 的返回值会被放入 pageProps 中。

再看看 invalidKeys 相干局部,除了 revalidatepropsredirectnotFound 外别的属性都会被视为非法。

const invalidKeys = Object.keys(data).filter(    key => key !== 'revalidate' && key !== 'props' && key !== 'redirect' && key !== 'notFound');if (invalidKeys.includes('unstable_revalidate')) {    throw new Error(UNSTABLE_REVALIDATE_RENAME_ERROR);}if (invalidKeys.length) {    throw new Error(invalidKeysMsg('getStaticProps', invalidKeys));}

而后还有对于 notFoundredirect 的解决:

if ('notFound' in data && data.notFound) {    if (pathname === '/404') {        throw new Error(`The /404 page can not return notFound in "getStaticProps", please remove it to continue!`);    }    (renderOpts as any).isNotFound = true;}if ('redirect' in data && data.redirect && typeof data.redirect === 'object') {    checkRedirectValues(data.redirect as Redirect, req, 'getStaticProps');    if (isBuildTimeSSG) {        throw new Error(            `\`redirect\` can not be returned from getStaticProps during prerendering (${req.url})\n` +                `See more info here: https://nextjs.org/docs/messages/gsp-redirect-during-prerender`        );    }    (data as any).props = {        __N_REDIRECT: data.redirect.destination,        __N_REDIRECT_STATUS: getRedirectStatus(data.redirect)    };    if (typeof data.redirect.basePath !== 'undefined') {        (data as any).props.__N_REDIRECT_BASE_PATH = data.redirect.basePath;    }    (renderOpts as any).isRedirect = true;}

notFound 会应用 renderOpts.isNotFound 来标识,而 redirect 则会在 props 中通过 __N_REDIRECT 相干的参数来进行标识。

当然这里省略很多的校验,比方 getStaticPropsgetServerSideProps 抵触、getStaticPaths 的查看、notFoundredirect 不能同时存在等。

props.pageProps = Object.assign({}, props.pageProps, 'props' in data ? data.props : undefined);

而后其中还蕴含了一部分与 revalidate 相干的内容,次要是一些检测和值的解决,次要与 ISR 相干的此处先跳过。

getStaticPaths

getStaticPaths 的相干的调用源码次要在 packages/next/build/utils.ts 文件中的 buildStaticPaths 中,buildStaticPaths 会在两个时候被调用,一个是 next.js 构建的时候,第二个是 next.jsdevServer 中。在 next.js 遇到动静路由时,会依照 buildStaticPathsgetStaticProps 来决定是否启用 SSG 模式,启用则会调用 buildStaticPaths 获取该动静路由所对应的须要构建的所有动态页面。

if (getStaticPaths) {    staticPathsResult = await getStaticPaths({ locales, defaultLocale });}if (!staticPathsResult || typeof staticPathsResult !== 'object' || Array.isArray(staticPathsResult)) {    throw new Error(        `Invalid value returned from getStaticPaths in ${page}. Received ${typeof staticPathsResult} ${expectedReturnVal}`    );}const invalidStaticPathKeys = Object.keys(staticPathsResult).filter(key => !(key === 'paths' || key === 'fallback'));if (invalidStaticPathKeys.length > 0) {    throw new Error(        `Extra keys returned from getStaticPaths in ${page} (${invalidStaticPathKeys.join(', ')}) ${expectedReturnVal}`    );}if (!(typeof staticPathsResult.fallback === 'boolean' || staticPathsResult.fallback === 'blocking')) {    throw new Error(`The \`fallback\` key must be returned from getStaticPaths in ${page}.\n` + expectedReturnVal);}const toPrerender = staticPathsResult.paths;if (!Array.isArray(toPrerender)) {    throw new Error(        `Invalid \`paths\` value returned from getStaticPaths in ${page}.\n` +            `\`paths\` must be an array of strings or objects of shape { params: [key: string]: string }`    );}

buildStaticPaths 第一局部是获取 getStaticPaths 的返回值,并对其返回值进行查看:

  1. getStaticPaths 能够为 async 办法
  2. getStaticPaths 承受两个参数:localesdefaultLocale
  3. 返回值必须为 {paths: Array, fallback: boolean | 'blocking'} 构造

而在拿到 toPrerender 之后,next.js 会将其转换为 prerenderPathsencodedPrerenderPaths,这两个 set 的数据集基本一致,只是一个 path 为曾经被解码,一个没有,猜想是为了性能思考空间换工夫。

toPrerender.forEach(entry => {    if (typeof entry === 'string') {        entry = removeTrailingSlash(entry);        const localePathResult = normalizeLocalePath(entry, locales);        let cleanedEntry = entry;        if (localePathResult.detectedLocale) {            cleanedEntry = entry.slice(localePathResult.detectedLocale.length + 1);        } else if (defaultLocale) {            entry = `/${defaultLocale}${entry}`;        }        const result = _routeMatcher(cleanedEntry);        if (!result) {            throw new Error(`The provided path \`${cleanedEntry}\` does not match the page: \`${page}\`.`);        }        // If leveraging the string paths variant the entry should already be        // encoded so we decode the segments ensuring we only escape path        // delimiters        prerenderPaths.add(            entry                .split('/')                .map(segment => escapePathDelimiters(decodeURIComponent(segment), true))                .join('/')        );        encodedPrerenderPaths.add(entry);    } else {        // ...    }});

针对 string 类型的 entry,简略的解决下语言、门路即可。

const _validParamKeys = Object.keys(_routeMatcher(page));if (typeof entry === 'string') {    // ...} else {    const invalidKeys = Object.keys(entry).filter(key => key !== 'params' && key !== 'locale');    if (invalidKeys.length) {        throw new Error('...');    }    const { params = {} } = entry;    let builtPage = page;    let encodedBuiltPage = page;    _validParamKeys.forEach(validParamKey => {        const { repeat, optional } = _routeRegex.groups[validParamKey];        let paramValue = params[validParamKey];        if (            optional &&            params.hasOwnProperty(validParamKey) &&            (paramValue === null || paramValue === undefined || (paramValue as any) === false)        ) {            paramValue = [];        }        if ((repeat && !Array.isArray(paramValue)) || (!repeat && typeof paramValue !== 'string')) {            throw new Error('...');        }        let replaced = `[${repeat ? '...' : ''}${validParamKey}]`;        if (optional) {            replaced = `[${replaced}]`;        }        builtPage = builtPage            .replace(                replaced,                repeat                    ? (paramValue as string[]).map(segment => escapePathDelimiters(segment, true)).join('/')                    : escapePathDelimiters(paramValue as string, true)            )            .replace(/(?!^)\/$/, '');        encodedBuiltPage = encodedBuiltPage            .replace(                replaced,                repeat                    ? (paramValue as string[]).map(encodeURIComponent).join('/')                    : encodeURIComponent(paramValue as string)            )            .replace(/(?!^)\/$/, '');    });    if (entry.locale && !locales?.includes(entry.locale)) {        throw new Error('...');    }    const curLocale = entry.locale || defaultLocale || '';    prerenderPaths.add(`${curLocale ? `/${curLocale}` : ''}${curLocale && builtPage === '/' ? '' : builtPage}`);    encodedPrerenderPaths.add(        `${curLocale ? `/${curLocale}` : ''}${curLocale && encodedBuiltPage === '/' ? '' : encodedBuiltPage}`    );}

而对于 Object 类型的 entry,则会先查看确保是 {params, locale} 构造,而后应用 params 对动静路由进行替换拼接。 _validParamKeys 是该动静路由页面中的参数的 key 数组。而后一样是门路和语言的解决。最终的返回值如下:

return {    paths: [...prerenderPaths],    fallback: staticPathsResult.fallback,    encodedPaths: [...encodedPrerenderPaths]};

当须要时 next.js 就会应用这里的 paths 来生成对应的动态页面,从而实现动静路由的 SSG

总结

getStaticPropsgetStaticPaths 相干的源码其实大部分都是在解决对于数据查看、解决这类的事件,因为这两个 API 的指摘也都很简略:getStaticPaths 负责为动静路由的 SSG 场景提供页面列表,getStaticProps 则为 SSG 页面提供对应的页面数据。