rsnext/packages/next/client/page-loader.ts

487 lines
16 KiB
TypeScript
Raw Normal View History

import { ComponentType } from 'react'
import type { ClientSsgManifest } from '../build'
import type { ClientBuildManifest } from '../build/webpack/plugins/build-manifest-plugin'
import mitt from '../next-server/lib/mitt'
import type { MittEmitter } from '../next-server/lib/mitt'
import {
addBasePath,
markLoadingError,
interpolateAs,
} from '../next-server/lib/router/router'
import getAssetPathFromRoute from '../next-server/lib/router/utils/get-asset-path-from-route'
import { isDynamicRoute } from '../next-server/lib/router/utils/is-dynamic'
import { parseRelativeUrl } from '../next-server/lib/router/utils/parse-relative-url'
import { searchParamsToUrlQuery } from '../next-server/lib/router/utils/querystring'
2020-08-27 04:13:28 +02:00
export const looseToArray = <T extends {}>(input: any): T[] =>
[].slice.call(input)
function getInitialStylesheets(): StyleSheetTuple[] {
return looseToArray<CSSStyleSheet>(document.styleSheets)
.filter(
(el: CSSStyleSheet) =>
el.ownerNode &&
(el.ownerNode as Element).tagName === 'LINK' &&
(el.ownerNode as Element).hasAttribute('data-n-p')
)
.map((sheet) => ({
href: (sheet.ownerNode as Element).getAttribute('href')!,
text: looseToArray<CSSRule>(sheet.cssRules)
.map((r) => r.cssText)
.join(''),
}))
}
function hasRel(rel: string, link?: HTMLLinkElement) {
try {
link = document.createElement('link')
2019-12-24 21:40:06 +01:00
return link.relList.supports(rel)
} catch {}
}
function pageLoadError(route: string) {
return markLoadingError(new Error(`Error loading ${route}`))
}
2019-12-24 21:40:06 +01:00
const relPrefetch =
hasRel('preload') && !hasRel('prefetch')
? // https://caniuse.com/#feat=link-rel-preload
// macOS and iOS (Safari does not support prefetch)
'preload'
: // https://caniuse.com/#feat=link-rel-prefetch
// IE 11, Edge 12+, nearly all evergreen
'prefetch'
const relPreload = hasRel('preload') ? 'preload' : relPrefetch
const relPreloadStyle = 'fetch'
const hasNoModule = 'noModule' in document.createElement('script')
function normalizeRoute(route: string) {
if (route[0] !== '/') {
throw new Error(`Route name should start with a "/", got "${route}"`)
}
if (route === '/') return route
return route.replace(/\/$/, '')
}
function appendLink(
href: string,
rel: string,
as?: string,
link?: HTMLLinkElement
): Promise<any> {
return new Promise((res, rej) => {
link = document.createElement('link')
// The order of property assignment here is intentional:
if (as) link!.as = as
link!.rel = rel
link!.crossOrigin = process.env.__NEXT_CROSS_ORIGIN!
link!.onload = res
link!.onerror = rej
// `href` should always be last:
link!.href = href
document.head.appendChild(link)
})
2019-07-31 18:03:38 +02:00
}
function loadScript(url: string): Promise<any> {
return new Promise((res, rej) => {
const script = document.createElement('script')
if (process.env.__NEXT_MODERN_BUILD && hasNoModule) {
script.type = 'module'
}
script.crossOrigin = process.env.__NEXT_CROSS_ORIGIN!
script.src = url
script.onload = res
script.onerror = () => rej(pageLoadError(url))
document.body.appendChild(script)
})
}
export type StyleSheetTuple = { href: string; text: string }
export type GoodPageCache = {
page: ComponentType
mod: any
styleSheets: StyleSheetTuple[]
}
2020-08-13 06:01:15 +02:00
export type PageCacheEntry = { error: any } | GoodPageCache
export default class PageLoader {
private initialPage: string
private buildId: string
private assetPrefix: string
private pageCache: Record<string, PageCacheEntry>
private pageRegisterEvents: MittEmitter
private loadingRoutes: Record<string, boolean>
private promisedBuildManifest?: Promise<ClientBuildManifest>
private promisedSsgManifest?: Promise<ClientSsgManifest>
private promisedDevPagesManifest?: Promise<any>
2020-08-27 04:13:28 +02:00
constructor(buildId: string, assetPrefix: string, initialPage: string) {
this.initialPage = initialPage
this.buildId = buildId
this.assetPrefix = assetPrefix
this.pageCache = {}
this.pageRegisterEvents = mitt()
this.loadingRoutes = {
// By default these 2 pages are being loaded in the initial html
'/_app': true,
}
// TODO: get rid of this limitation for rendering the error page
if (initialPage !== '/_error') {
this.loadingRoutes[initialPage] = true
}
this.promisedBuildManifest = new Promise((resolve) => {
if ((window as any).__BUILD_MANIFEST) {
resolve((window as any).__BUILD_MANIFEST)
} else {
;(window as any).__BUILD_MANIFEST_CB = () => {
resolve((window as any).__BUILD_MANIFEST)
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
}
}
})
/** @type {Promise<Set<string>>} */
2020-05-18 21:24:37 +02:00
this.promisedSsgManifest = new Promise((resolve) => {
if ((window as any).__SSG_MANIFEST) {
resolve((window as any).__SSG_MANIFEST)
} else {
;(window as any).__SSG_MANIFEST_CB = () => {
resolve((window as any).__SSG_MANIFEST)
}
}
})
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
}
getPageList() {
if (process.env.NODE_ENV === 'production') {
return this.promisedBuildManifest!.then(
(buildManifest) => buildManifest.sortedPages
)
} else {
if ((window as any).__DEV_PAGES_MANIFEST) {
return (window as any).__DEV_PAGES_MANIFEST.pages
} else {
if (!this.promisedDevPagesManifest) {
this.promisedDevPagesManifest = fetch(
`${this.assetPrefix}/_next/static/development/_devPagesManifest.json`
)
.then((res) => res.json())
.then((manifest) => {
;(window as any).__DEV_PAGES_MANIFEST = manifest
return manifest.pages
})
.catch((err) => {
console.log(`Failed to fetch devPagesManifest`, err)
})
}
return this.promisedDevPagesManifest
}
}
}
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
// Returns a promise for the dependencies for a particular route
private getDependencies(route: string): Promise<string[]> {
return this.promisedBuildManifest!.then((m) => {
return m[route]
? m[route].map((url) => `${this.assetPrefix}/_next/${encodeURI(url)}`)
: Promise.reject(pageLoadError(route))
})
}
/**
* @param {string} href the route href (file-system path)
* @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes
*/
getDataHref(href: string, asPath: string, ssg: boolean) {
const { pathname: hrefPathname, searchParams, search } = parseRelativeUrl(
href
)
const query = searchParamsToUrlQuery(searchParams)
const { pathname: asPathname } = parseRelativeUrl(asPath)
const route = normalizeRoute(hrefPathname)
const getHrefForSlug = (path: string) => {
const dataRoute = getAssetPathFromRoute(path, '.json')
return addBasePath(
`/_next/data/${this.buildId}${dataRoute}${ssg ? '' : search}`
)
}
const isDynamic: boolean = isDynamicRoute(route)
const interpolatedRoute = isDynamic
? interpolateAs(hrefPathname, asPathname, query)
: ''
return isDynamic
? interpolatedRoute && getHrefForSlug(interpolatedRoute)
: getHrefForSlug(route)
}
/**
* @param {string} href the route href (file-system path)
* @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes
*/
prefetchData(href: string, asPath: string) {
const { pathname: hrefPathname } = parseRelativeUrl(href)
const route = normalizeRoute(hrefPathname)
return this.promisedSsgManifest!.then(
2020-09-01 20:09:25 +02:00
(s: ClientSsgManifest, _dataHref?: string) =>
// Check if the route requires a data file
s.has(route) &&
// Try to generate data href, noop when falsy
(_dataHref = this.getDataHref(href, asPath, true)) &&
// noop when data has already been prefetched (dedupe)
!document.querySelector(
`link[rel="${relPrefetch}"][href^="${_dataHref}"]`
) &&
// Inject the `<link rel=prefetch>` tag for above computed `href`.
appendLink(_dataHref, relPrefetch, 'fetch').catch(() => {
/* ignore prefetch error */
})
)
}
2020-08-13 06:01:15 +02:00
loadPage(route: string): Promise<GoodPageCache> {
route = normalizeRoute(route)
2020-08-13 06:01:15 +02:00
return new Promise<GoodPageCache>((resolve, reject) => {
// If there's a cached version of the page, let's use it.
const cachedPage = this.pageCache[route]
if (cachedPage) {
2020-08-13 06:01:15 +02:00
if ('error' in cachedPage) {
reject(cachedPage.error)
} else {
resolve(cachedPage)
}
return
}
2020-08-13 06:01:15 +02:00
const fire = (pageToCache: PageCacheEntry) => {
this.pageRegisterEvents.off(route, fire)
delete this.loadingRoutes[route]
2020-08-13 06:01:15 +02:00
if ('error' in pageToCache) {
reject(pageToCache.error)
} else {
2020-08-13 06:01:15 +02:00
resolve(pageToCache)
}
}
// Register a listener to get the page
this.pageRegisterEvents.on(route, fire)
if (!this.loadingRoutes[route]) {
this.loadingRoutes[route] = true
if (process.env.NODE_ENV === 'production') {
this.getDependencies(route)
.then((deps) => {
const pending: Promise<any>[] = []
deps.forEach((d) => {
if (
d.endsWith('.js') &&
!document.querySelector(`script[src^="${d}"]`)
) {
pending.push(loadScript(d))
}
// Prefetch CSS as it'll be needed when the page JavaScript
// evaluates. This will only trigger if explicit prefetching is
// disabled for a <Link>... prefetching in this case is desirable
// because we *know* it's going to be used very soon (page was
// loaded).
if (
d.endsWith('.css') &&
!document.querySelector(
`link[rel="${relPreload}"][href^="${d}"]`
)
) {
// This is not pushed into `pending` because we don't need to
// wait for these to resolve. To prevent an unhandled
// rejection, we swallow the error which is handled later in
// the rendering cycle (this is just a preload optimization).
appendLink(d, relPreload, relPreloadStyle).catch(() => {
/* ignore preload error */
})
}
})
return Promise.all(pending)
})
.catch((err) => {
// Mark the page as failed to load if any of its required scripts
// fail to load:
this.pageCache[route] = { error: err }
fire({ error: err })
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
})
} else {
// Development only. In production the page file is part of the build manifest
route = normalizeRoute(route)
let scriptRoute = getAssetPathFromRoute(route, '.js')
const url = `${this.assetPrefix}/_next/static/chunks/pages${encodeURI(
scriptRoute
)}`
loadScript(url).catch((err) => {
// Mark the page as failed to load if its script fails to load:
this.pageCache[route] = { error: err }
fire({ error: err })
})
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
}
}
})
}
// This method if called by the route code.
registerPage(route: string, regFn: () => any) {
const register = (styleSheets: StyleSheetTuple[]) => {
try {
const mod = regFn()
const pageData: PageCacheEntry = {
page: mod.default || mod,
mod,
styleSheets,
}
this.pageCache[route] = pageData
this.pageRegisterEvents.emit(route, pageData)
} catch (error) {
this.pageCache[route] = { error }
this.pageRegisterEvents.emit(route, { error })
}
}
if (process.env.NODE_ENV !== 'production') {
// Wait for webpack to become idle if it's not.
// More info: https://github.com/vercel/next.js/pull/1511
if ((module as any).hot && (module as any).hot.status() !== 'idle') {
console.log(
`Waiting for webpack to become "idle" to initialize the page: "${route}"`
)
const check = (status: string) => {
if (status === 'idle') {
;(module as any).hot.removeStatusHandler(check)
register(
/* css is handled via style-loader in development */
[]
)
}
}
;(module as any).hot.status(check)
return
}
}
function fetchStyleSheet(href: string): Promise<StyleSheetTuple> {
return fetch(href).then((res) => {
if (!res.ok) throw pageLoadError(href)
return res.text().then((text) => ({ href, text }))
})
}
2020-08-27 04:13:28 +02:00
const isInitialLoad = route === this.initialPage
const promisedDeps: Promise<StyleSheetTuple[]> =
// Shared styles will already be on the page:
route === '/_app' ||
// We use `style-loader` in development:
process.env.NODE_ENV !== 'production'
? Promise.resolve([])
: // Tests that this does not block hydration:
// test/integration/css-fixtures/hydrate-without-deps/
2020-08-27 04:13:28 +02:00
(isInitialLoad
? Promise.resolve(
looseToArray<HTMLLinkElement>(
document.querySelectorAll('link[data-n-p]')
).map((e) => e.getAttribute('href')!)
)
: this.getDependencies(route).then((deps) =>
deps.filter((d) => d.endsWith('.css'))
)
).then((cssFiles) =>
// These files should've already been fetched by now, so this
// should resolve instantly.
Promise.all(cssFiles.map((d) => fetchStyleSheet(d))).catch(
(err) => {
if (isInitialLoad) return getInitialStylesheets()
throw err
}
)
2020-08-27 04:13:28 +02:00
)
promisedDeps.then(
(deps) => register(deps),
(error) => {
this.pageCache[route] = { error }
this.pageRegisterEvents.emit(route, { error })
}
)
}
/**
* @param {string} route
* @param {boolean} [isDependency]
*/
prefetch(route: string, isDependency?: boolean): Promise<void> {
// https://github.com/GoogleChromeLabs/quicklink/blob/453a661fa1fa940e2d2e044452398e38c67a98fb/src/index.mjs#L115-L118
// License: Apache 2.0
2019-07-31 18:03:38 +02:00
let cn
if ((cn = (navigator as any).connection)) {
// Don't prefetch if using 2G or if Save-Data is enabled.
if (cn.saveData || /2g/.test(cn.effectiveType)) return Promise.resolve()
}
/** @type {string} */
let url
if (isDependency) {
url = route
} else {
if (process.env.NODE_ENV !== 'production') {
route = normalizeRoute(route)
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
const ext =
process.env.__NEXT_MODERN_BUILD && hasNoModule ? '.module.js' : '.js'
const scriptRoute = getAssetPathFromRoute(route, ext)
url = `${this.assetPrefix}/_next/static/${encodeURIComponent(
this.buildId
)}/pages${encodeURI(scriptRoute)}`
}
Experimental: Granular build chunking (#7696) * Refactor SplitChunksPlugin configs and add experimental chunking strategy * Use typeDefs for SplitChunksConfig * Modify build manifest plugin to create runtime build manifest * Add support for granular chunks to page-loader * Ensure normal behavior if experimental granularChunks flag is false * Update client build manifest to remove iife & implicit global * Factor out '/_next/' prepending into getDependencies * Update packages/next/build/webpack-config.ts filepath regex Co-Authored-By: Jason Miller <developit@users.noreply.github.com> * Simplify dependency load ordering in page-loader.js * Use SHA1 hash to shorten filenames for dependency modules * Add scheduler to framework cacheGroup in webpack-config * Update page loader to not duplicate script tags with query parameters * Ensure no slashes end up in the file hashes * Add prop-types to framework chunk * Fix issue with mis-attributed events * Increase modern build size budget--possibly decrement after consulting with @janicklasralph * Use module.rawRequest for lib chunks Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Dasherize lib chunk names Co-Authored-By: Daniel Stockman <daniel.stockman@gmail.com> * Fix typescript errors, reorganize lib name logic * Dasherize rawRequest, short circuit name logic when rawRequest found * Add `scheduler` package to test regex * Fix a nit * Adjust build manifest plugin * Shorten key name * Extract createPreloadLink helper * Extract getDependencies helper * Move method * Minimize diff * Minimize diff x2 * Fix Array.from polyfill * Simplify page loader code * Remove async=false for script tags * Code golf `getDependencies` implementation * Require lib chunks be in node_modules * Update packages/next/build/webpack-config.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Replace remaining missed windows compat regex * Trim client manifest * Prevent duplicate link preload tags * Revert size test changes * Squash manifest size even further * Add comment for clarity * Code golfing 🏌️‍♂️ * Correctly select modern dependencies * Ship separate modern client manifest when module/module enabled * Update packages/next/build/webpack/plugins/build-manifest-plugin.ts Co-Authored-By: Joe Haddad <timer150@gmail.com> * Remove unneccessary filter from page-loader * Add lookbehind to file extension regex in page-loader * v9.0.3 * Update examples for Apollo with AppTree (#8180) * Update examples for Apollo with AppTree * Fix apolloClient being overwritten when rendering AppTree * Golf page-loader (#8190) * Remove lookbehind for module replacement * Wait for build manifest promise before page load or prefetch * Updating modern-only chunks inside the right entry point * Fixing ts errors * Rename variable * Revert "Wait for build manifest promise before page load or prefetch" This reverts commit c370528c6888ba7fa71162a0854534ed280224ef. * Use proper typedef for webpack chunk * Re-enable promisified client build manifest * Fix bug in getDependencies map * Insert check for granularChunks in page-loader * Increase size limit temporarily for granular chunks * Add 50ms delay to flaky test * Set env.__NEXT_GRANULAR_CHUNKS in webpack config * Reset size limit to 187 * Set process.env.__NEXT_GRANULAR_CHUNKS to false if selectivePageBuilding * Update test/integration/production/test/index.test.js Co-Authored-By: Joe Haddad <timer150@gmail.com> * Do not create promise if not using chunking PR
2019-08-08 19:14:33 +02:00
}
return Promise.all(
document.querySelector(`link[rel="${relPrefetch}"][href^="${url}"]`)
? []
: [
url &&
appendLink(
url,
relPrefetch,
url.endsWith('.css') ? relPreloadStyle : 'script'
),
process.env.NODE_ENV === 'production' &&
!isDependency &&
2020-05-18 21:24:37 +02:00
this.getDependencies(route).then((urls) =>
Promise.all(
urls.map((dependencyUrl) =>
this.prefetch(dependencyUrl, true)
)
)
),
]
).then(
// do not return any data
() => {},
// swallow prefetch errors
() => {}
)
}
}