UNPKG

7.11 kBJavaScriptView Raw
1"use strict";var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");exports.__esModule=true;exports.markAssetError=markAssetError;exports.isAssetError=isAssetError;exports.getClientBuildManifest=getClientBuildManifest;exports.default=void 0;var _getAssetPathFromRoute=_interopRequireDefault(require("../next-server/lib/router/utils/get-asset-path-from-route"));var _requestIdleCallback=require("./request-idle-callback");// 3.8s was arbitrarily chosen as it's what https://web.dev/interactive
2// considers as "Good" time-to-interactive. We must assume something went
3// wrong beyond this point, and then fall-back to a full page transition to
4// show the user something of value.
5const MS_MAX_IDLE_DELAY=3800;function withFuture(key,map,generator){let entry=map.get(key);if(entry){if('future'in entry){return entry.future;}return Promise.resolve(entry);}let resolver;const prom=new Promise(resolve=>{resolver=resolve;});map.set(key,entry={resolve:resolver,future:prom});return generator?// eslint-disable-next-line no-sequences
6generator().then(value=>(resolver(value),value)):prom;}function hasPrefetch(link){try{link=document.createElement('link');return(// detect IE11 since it supports prefetch but isn't detected
7// with relList.support
8!!window.MSInputMethodContext&&!!document.documentMode||link.relList.supports('prefetch'));}catch(_unused){return false;}}const canPrefetch=hasPrefetch();function prefetchViaDom(href,as,link){return new Promise((res,rej)=>{if(document.querySelector(`link[rel="prefetch"][href^="${href}"]`)){return res();}link=document.createElement('link');// The order of property assignment here is intentional:
9if(as)link.as=as;link.rel=`prefetch`;link.crossOrigin=process.env.__NEXT_CROSS_ORIGIN;link.onload=res;link.onerror=rej;// `href` should always be last:
10link.href=href;document.head.appendChild(link);});}const ASSET_LOAD_ERROR=Symbol('ASSET_LOAD_ERROR');// TODO: unexport
11function markAssetError(err){return Object.defineProperty(err,ASSET_LOAD_ERROR,{});}function isAssetError(err){return err&&ASSET_LOAD_ERROR in err;}function appendScript(src,script){return new Promise((resolve,reject)=>{script=document.createElement('script');// The order of property assignment here is intentional.
12// 1. Setup success/failure hooks in case the browser synchronously
13// executes when `src` is set.
14script.onload=resolve;script.onerror=()=>reject(markAssetError(new Error(`Failed to load script: ${src}`)));// 2. Configure the cross-origin attribute before setting `src` in case the
15// browser begins to fetch.
16script.crossOrigin=process.env.__NEXT_CROSS_ORIGIN;// 3. Finally, set the source and inject into the DOM in case the child
17// must be appended for fetching to start.
18script.src=src;document.body.appendChild(script);});}// Resolve a promise that times out after given amount of milliseconds.
19function resolvePromiseWithTimeout(p,ms,err){return new Promise((resolve,reject)=>{let cancelled=false;p.then(r=>{// Resolved, cancel the timeout
20cancelled=true;resolve(r);}).catch(reject);(0,_requestIdleCallback.requestIdleCallback)(()=>setTimeout(()=>{if(!cancelled){reject(err);}},ms));});}// TODO: stop exporting or cache the failure
21// It'd be best to stop exporting this. It's an implementation detail. We're
22// only exporting it for backwards compatibilty with the `page-loader`.
23// Only cache this response as a last resort if we cannot eliminate all other
24// code branches that use the Build Manifest Callback and push them through
25// the Route Loader interface.
26function getClientBuildManifest(){if(self.__BUILD_MANIFEST){return Promise.resolve(self.__BUILD_MANIFEST);}const onBuildManifest=new Promise(resolve=>{// Mandatory because this is not concurrent safe:
27const cb=self.__BUILD_MANIFEST_CB;self.__BUILD_MANIFEST_CB=()=>{resolve(self.__BUILD_MANIFEST);cb&&cb();};});return resolvePromiseWithTimeout(onBuildManifest,MS_MAX_IDLE_DELAY,markAssetError(new Error('Failed to load client build manifest')));}function getFilesForRoute(assetPrefix,route){if(process.env.NODE_ENV==='development'){return Promise.resolve({scripts:[assetPrefix+'/_next/static/chunks/pages'+encodeURI((0,_getAssetPathFromRoute.default)(route,'.js'))],// Styles are handled by `style-loader` in development:
28css:[]});}return getClientBuildManifest().then(manifest=>{if(!(route in manifest)){throw markAssetError(new Error(`Failed to lookup route: ${route}`));}const allFiles=manifest[route].map(entry=>assetPrefix+'/_next/'+encodeURI(entry));return{scripts:allFiles.filter(v=>v.endsWith('.js')),css:allFiles.filter(v=>v.endsWith('.css'))};});}function createRouteLoader(assetPrefix){const entrypoints=new Map();const loadedScripts=new Map();const styleSheets=new Map();const routes=new Map();function maybeExecuteScript(src){let prom=loadedScripts.get(src);if(prom){return prom;}// Skip executing script if it's already in the DOM:
29if(document.querySelector(`script[src^="${src}"]`)){return Promise.resolve();}loadedScripts.set(src,prom=appendScript(src));return prom;}function fetchStyleSheet(href){let prom=styleSheets.get(href);if(prom){return prom;}styleSheets.set(href,prom=fetch(href).then(res=>{if(!res.ok){throw new Error(`Failed to load stylesheet: ${href}`);}return res.text().then(text=>({href:href,content:text}));}).catch(err=>{throw markAssetError(err);}));return prom;}return{whenEntrypoint(route){return withFuture(route,entrypoints);},onEntrypoint(route,execute){Promise.resolve(execute).then(fn=>fn()).then(exports=>({component:exports&&exports.default||exports,exports:exports}),err=>({error:err})).then(input=>{const old=entrypoints.get(route);entrypoints.set(route,input);if(old&&'resolve'in old)old.resolve(input);});},loadRoute(route,prefetch){return withFuture(route,routes,()=>{return resolvePromiseWithTimeout(getFilesForRoute(assetPrefix,route).then(({scripts,css})=>{return Promise.all([entrypoints.has(route)?[]:Promise.all(scripts.map(maybeExecuteScript)),Promise.all(css.map(fetchStyleSheet))]);}).then(res=>{return this.whenEntrypoint(route).then(entrypoint=>({entrypoint,styles:res[1]}));}),MS_MAX_IDLE_DELAY,markAssetError(new Error(`Route did not complete loading: ${route}`))).then(({entrypoint,styles})=>{const res=Object.assign({styles:styles},entrypoint);return'error'in entrypoint?entrypoint:res;}).catch(err=>{if(prefetch){// we don't want to cache errors during prefetch
30throw err;}return{error:err};});});},prefetch(route){// https://github.com/GoogleChromeLabs/quicklink/blob/453a661fa1fa940e2d2e044452398e38c67a98fb/src/index.mjs#L115-L118
31// License: Apache 2.0
32let cn;if(cn=navigator.connection){// Don't prefetch if using 2G or if Save-Data is enabled.
33if(cn.saveData||/2g/.test(cn.effectiveType))return Promise.resolve();}return getFilesForRoute(assetPrefix,route).then(output=>Promise.all(canPrefetch?output.scripts.map(script=>prefetchViaDom(script,'script')):[])).then(()=>{(0,_requestIdleCallback.requestIdleCallback)(()=>this.loadRoute(route,true).catch(()=>{}));}).catch(// swallow prefetch errors
34()=>{});}};}var _default=createRouteLoader;exports.default=_default;
35//# sourceMappingURL=route-loader.js.map
\No newline at end of file