next.js 源码解析 - getStaticProps、getStaticPaths 篇
好久前寫了關于
getStaticProps和getStaticPaths的內容,然而半年過去了源碼解析就一直忘記了,不久前有人提醒才想起來,補下坑。
本文主要是解讀下 getStaticProps、getStaticPaths 相關的源碼,不了解這兩個 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是在動態頁面的路由參數previewData和preview: preview模式的相關數據locales, locale和defaultLocale多語言相關參數
執行完成后 getStaticProps 的返回值會被放入 pageProps 中。
再看看 invalidKeys 相關部分,除了 revalidate、props、redirect 和 notFound 外別的屬性都會被視為非法。
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));
}
然后還有關于 notFound 和 redirect 的處理:
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 相關的參數來進行標識。
當然這里省略很多的校驗,比如 getStaticProps 和 getServerSideProps 沖突、getStaticPaths 的檢查、notFound 和 redirect 不能同時存在等。
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.js 的 devServer 中。在 next.js 遇到動態路由時,會按照 buildStaticPaths 和 getStaticProps 來決定是否啟用 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 的返回值,并對其返回值進行檢查:
getStaticPaths可以為async方法getStaticPaths接受兩個參數:locales和defaultLocale- 返回值必須為
{paths: Array, fallback: boolean | 'blocking'}結構
而在拿到 toPrerender 之后,next.js 會將其轉換為 prerenderPaths 和 encodedPrerenderPaths,這兩個 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。
總結
getStaticProps、getStaticPaths 相關的源碼其實大部分都是在處理關于數據檢查、處理這類的事情,因為這兩個 API 的指責也都很簡單:getStaticPaths 負責為動態路由的 SSG 場景提供頁面列表,getStaticProps 則為 SSG 頁面提供對應的頁面數據。
總結
以上是生活随笔為你收集整理的next.js 源码解析 - getStaticProps、getStaticPaths 篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eclipse使用技巧和插件
- 下一篇: spyder错误合集