route-loader.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.markAssetError = markAssetError;
  6. exports.isAssetError = isAssetError;
  7. exports.getClientBuildManifest = getClientBuildManifest;
  8. exports.createRouteLoader = createRouteLoader;
  9. var _interop_require_default = require("@swc/helpers/lib/_interop_require_default.js").default;
  10. var _getAssetPathFromRoute = _interop_require_default(require("../shared/lib/router/utils/get-asset-path-from-route"));
  11. var _trustedTypes = require("./trusted-types");
  12. var _requestIdleCallback = require("./request-idle-callback");
  13. // 3.8s was arbitrarily chosen as it's what https://web.dev/interactive
  14. // considers as "Good" time-to-interactive. We must assume something went
  15. // wrong beyond this point, and then fall-back to a full page transition to
  16. // show the user something of value.
  17. const MS_MAX_IDLE_DELAY = 3800;
  18. function withFuture(key, map, generator) {
  19. let entry = map.get(key);
  20. if (entry) {
  21. if ('future' in entry) {
  22. return entry.future;
  23. }
  24. return Promise.resolve(entry);
  25. }
  26. let resolver;
  27. const prom = new Promise((resolve)=>{
  28. resolver = resolve;
  29. });
  30. map.set(key, entry = {
  31. resolve: resolver,
  32. future: prom
  33. });
  34. return generator ? generator()// eslint-disable-next-line no-sequences
  35. .then((value)=>(resolver(value), value)).catch((err)=>{
  36. map.delete(key);
  37. throw err;
  38. }) : prom;
  39. }
  40. function hasPrefetch(link) {
  41. try {
  42. link = document.createElement('link');
  43. return(// detect IE11 since it supports prefetch but isn't detected
  44. // with relList.support
  45. (!!window.MSInputMethodContext && !!document.documentMode) || link.relList.supports('prefetch'));
  46. } catch (e) {
  47. return false;
  48. }
  49. }
  50. const canPrefetch = hasPrefetch();
  51. function prefetchViaDom(href, as, link) {
  52. return new Promise((res, rej)=>{
  53. const selector = `
  54. link[rel="prefetch"][href^="${href}"],
  55. link[rel="preload"][href^="${href}"],
  56. script[src^="${href}"]`;
  57. if (document.querySelector(selector)) {
  58. return res();
  59. }
  60. link = document.createElement('link');
  61. // The order of property assignment here is intentional:
  62. if (as) link.as = as;
  63. link.rel = `prefetch`;
  64. link.crossOrigin = process.env.__NEXT_CROSS_ORIGIN;
  65. link.onload = res;
  66. link.onerror = rej;
  67. // `href` should always be last:
  68. link.href = href;
  69. document.head.appendChild(link);
  70. });
  71. }
  72. const ASSET_LOAD_ERROR = Symbol('ASSET_LOAD_ERROR');
  73. function markAssetError(err) {
  74. return Object.defineProperty(err, ASSET_LOAD_ERROR, {});
  75. }
  76. function isAssetError(err) {
  77. return err && ASSET_LOAD_ERROR in err;
  78. }
  79. function appendScript(src, script) {
  80. return new Promise((resolve, reject)=>{
  81. script = document.createElement('script');
  82. // The order of property assignment here is intentional.
  83. // 1. Setup success/failure hooks in case the browser synchronously
  84. // executes when `src` is set.
  85. script.onload = resolve;
  86. script.onerror = ()=>reject(markAssetError(new Error(`Failed to load script: ${src}`)));
  87. // 2. Configure the cross-origin attribute before setting `src` in case the
  88. // browser begins to fetch.
  89. script.crossOrigin = process.env.__NEXT_CROSS_ORIGIN;
  90. // 3. Finally, set the source and inject into the DOM in case the child
  91. // must be appended for fetching to start.
  92. script.src = src;
  93. document.body.appendChild(script);
  94. });
  95. }
  96. // We wait for pages to be built in dev before we start the route transition
  97. // timeout to prevent an un-necessary hard navigation in development.
  98. let devBuildPromise;
  99. // Resolve a promise that times out after given amount of milliseconds.
  100. function resolvePromiseWithTimeout(p, ms, err) {
  101. return new Promise((resolve, reject)=>{
  102. let cancelled = false;
  103. p.then((r)=>{
  104. // Resolved, cancel the timeout
  105. cancelled = true;
  106. resolve(r);
  107. }).catch(reject);
  108. // We wrap these checks separately for better dead-code elimination in
  109. // production bundles.
  110. if (process.env.NODE_ENV === 'development') {
  111. (devBuildPromise || Promise.resolve()).then(()=>{
  112. (0, _requestIdleCallback).requestIdleCallback(()=>setTimeout(()=>{
  113. if (!cancelled) {
  114. reject(err);
  115. }
  116. }, ms));
  117. });
  118. }
  119. if (process.env.NODE_ENV !== 'development') {
  120. (0, _requestIdleCallback).requestIdleCallback(()=>setTimeout(()=>{
  121. if (!cancelled) {
  122. reject(err);
  123. }
  124. }, ms));
  125. }
  126. });
  127. }
  128. function getClientBuildManifest() {
  129. if (self.__BUILD_MANIFEST) {
  130. return Promise.resolve(self.__BUILD_MANIFEST);
  131. }
  132. const onBuildManifest = new Promise((resolve)=>{
  133. // Mandatory because this is not concurrent safe:
  134. const cb = self.__BUILD_MANIFEST_CB;
  135. self.__BUILD_MANIFEST_CB = ()=>{
  136. resolve(self.__BUILD_MANIFEST);
  137. cb && cb();
  138. };
  139. });
  140. return resolvePromiseWithTimeout(onBuildManifest, MS_MAX_IDLE_DELAY, markAssetError(new Error('Failed to load client build manifest')));
  141. }
  142. function getFilesForRoute(assetPrefix, route) {
  143. if (process.env.NODE_ENV === 'development') {
  144. const scriptUrl = assetPrefix + '/_next/static/chunks/pages' + encodeURI((0, _getAssetPathFromRoute).default(route, '.js'));
  145. return Promise.resolve({
  146. scripts: [
  147. (0, _trustedTypes).__unsafeCreateTrustedScriptURL(scriptUrl)
  148. ],
  149. // Styles are handled by `style-loader` in development:
  150. css: []
  151. });
  152. }
  153. return getClientBuildManifest().then((manifest)=>{
  154. if (!(route in manifest)) {
  155. throw markAssetError(new Error(`Failed to lookup route: ${route}`));
  156. }
  157. const allFiles = manifest[route].map((entry)=>assetPrefix + '/_next/' + encodeURI(entry));
  158. return {
  159. scripts: allFiles.filter((v)=>v.endsWith('.js')).map((v)=>(0, _trustedTypes).__unsafeCreateTrustedScriptURL(v)),
  160. css: allFiles.filter((v)=>v.endsWith('.css'))
  161. };
  162. });
  163. }
  164. function createRouteLoader(assetPrefix) {
  165. const entrypoints = new Map();
  166. const loadedScripts = new Map();
  167. const styleSheets = new Map();
  168. const routes = new Map();
  169. function maybeExecuteScript(src) {
  170. // With HMR we might need to "reload" scripts when they are
  171. // disposed and readded. Executing scripts twice has no functional
  172. // differences
  173. if (process.env.NODE_ENV !== 'development') {
  174. let prom = loadedScripts.get(src.toString());
  175. if (prom) {
  176. return prom;
  177. }
  178. // Skip executing script if it's already in the DOM:
  179. if (document.querySelector(`script[src^="${src}"]`)) {
  180. return Promise.resolve();
  181. }
  182. loadedScripts.set(src.toString(), prom = appendScript(src));
  183. return prom;
  184. } else {
  185. return appendScript(src);
  186. }
  187. }
  188. function fetchStyleSheet(href) {
  189. let prom = styleSheets.get(href);
  190. if (prom) {
  191. return prom;
  192. }
  193. styleSheets.set(href, prom = fetch(href).then((res)=>{
  194. if (!res.ok) {
  195. throw new Error(`Failed to load stylesheet: ${href}`);
  196. }
  197. return res.text().then((text)=>({
  198. href: href,
  199. content: text
  200. }));
  201. }).catch((err)=>{
  202. throw markAssetError(err);
  203. }));
  204. return prom;
  205. }
  206. return {
  207. whenEntrypoint (route) {
  208. return withFuture(route, entrypoints);
  209. },
  210. onEntrypoint (route, execute) {
  211. (execute ? Promise.resolve().then(()=>execute()).then((exports)=>({
  212. component: exports && exports.default || exports,
  213. exports: exports
  214. }), (err)=>({
  215. error: err
  216. })) : Promise.resolve(undefined)).then((input)=>{
  217. const old = entrypoints.get(route);
  218. if (old && 'resolve' in old) {
  219. if (input) {
  220. entrypoints.set(route, input);
  221. old.resolve(input);
  222. }
  223. } else {
  224. if (input) {
  225. entrypoints.set(route, input);
  226. } else {
  227. entrypoints.delete(route);
  228. }
  229. // when this entrypoint has been resolved before
  230. // the route is outdated and we want to invalidate
  231. // this cache entry
  232. routes.delete(route);
  233. }
  234. });
  235. },
  236. loadRoute (route, prefetch) {
  237. return withFuture(route, routes, ()=>{
  238. let devBuildPromiseResolve;
  239. if (process.env.NODE_ENV === 'development') {
  240. devBuildPromise = new Promise((resolve)=>{
  241. devBuildPromiseResolve = resolve;
  242. });
  243. }
  244. return resolvePromiseWithTimeout(getFilesForRoute(assetPrefix, route).then(({ scripts , css })=>{
  245. return Promise.all([
  246. entrypoints.has(route) ? [] : Promise.all(scripts.map(maybeExecuteScript)),
  247. Promise.all(css.map(fetchStyleSheet)),
  248. ]);
  249. }).then((res)=>{
  250. return this.whenEntrypoint(route).then((entrypoint)=>({
  251. entrypoint,
  252. styles: res[1]
  253. }));
  254. }), MS_MAX_IDLE_DELAY, markAssetError(new Error(`Route did not complete loading: ${route}`))).then(({ entrypoint , styles })=>{
  255. const res = Object.assign({
  256. styles: styles
  257. }, entrypoint);
  258. return 'error' in entrypoint ? entrypoint : res;
  259. }).catch((err)=>{
  260. if (prefetch) {
  261. // we don't want to cache errors during prefetch
  262. throw err;
  263. }
  264. return {
  265. error: err
  266. };
  267. }).finally(()=>{
  268. return devBuildPromiseResolve == null ? void 0 : devBuildPromiseResolve();
  269. });
  270. });
  271. },
  272. prefetch (route) {
  273. // https://github.com/GoogleChromeLabs/quicklink/blob/453a661fa1fa940e2d2e044452398e38c67a98fb/src/index.mjs#L115-L118
  274. // License: Apache 2.0
  275. let cn;
  276. if (cn = navigator.connection) {
  277. // Don't prefetch if using 2G or if Save-Data is enabled.
  278. if (cn.saveData || /2g/.test(cn.effectiveType)) return Promise.resolve();
  279. }
  280. return getFilesForRoute(assetPrefix, route).then((output)=>Promise.all(canPrefetch ? output.scripts.map((script)=>prefetchViaDom(script.toString(), 'script')) : [])).then(()=>{
  281. (0, _requestIdleCallback).requestIdleCallback(()=>this.loadRoute(route, true).catch(()=>{}));
  282. }).catch(// swallow prefetch errors
  283. ()=>{});
  284. }
  285. };
  286. }
  287. if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
  288. Object.defineProperty(exports.default, '__esModule', { value: true });
  289. Object.assign(exports.default, exports);
  290. module.exports = exports.default;
  291. }
  292. //# sourceMappingURL=route-loader.js.map