UNPKG

next

Version:

The React Framework

263 lines (262 loc) • 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = onDemandEntryHandler; exports.entries = exports.BUILT = exports.BUILDING = exports.ADDED = void 0; var _events = require("events"); var _path = require("path"); var _normalizePagePath = require("../normalize-page-path"); var _require = require("../require"); var _findPageFile = require("../lib/find-page-file"); var _getRouteFromEntrypoint = _interopRequireDefault(require("../get-route-from-entrypoint")); var _constants = require("../../lib/constants"); var _output = require("../../build/output"); var _utils = require("../../build/utils"); var _entries = require("../../build/entries"); function onDemandEntryHandler(watcher, multiCompiler, { pagesDir , nextConfig , maxInactiveAge , pagesBufferLength }) { const { compilers } = multiCompiler; const invalidator = new Invalidator(watcher, multiCompiler); let lastClientAccessPages = [ '' ]; let doneCallbacks = new _events.EventEmitter(); for (const compiler of compilers){ compiler.hooks.make.tap('NextJsOnDemandEntries', (_compilation)=>{ invalidator.startBuilding(); }); } function getPagePathsFromEntrypoints(type, entrypoints) { const pagePaths = []; for (const entrypoint of entrypoints.values()){ const page = (0, _getRouteFromEntrypoint).default(entrypoint.name); if (page) { pagePaths.push(`${type}${page}`); } } return pagePaths; } multiCompiler.hooks.done.tap('NextJsOnDemandEntries', (multiStats)=>{ if (invalidator.rebuildAgain) { return invalidator.doneBuilding(); } const [clientStats, serverStats, edgeServerStats] = multiStats.stats; const pagePaths = [ ...getPagePathsFromEntrypoints('client', clientStats.compilation.entrypoints), ...getPagePathsFromEntrypoints('server', serverStats.compilation.entrypoints), ...edgeServerStats ? getPagePathsFromEntrypoints('edge-server', edgeServerStats.compilation.entrypoints) : [], ]; for (const page of pagePaths){ const entry = entries[page]; if (!entry) { continue; } if (entry.status !== BUILDING) { continue; } entry.status = BUILT; doneCallbacks.emit(page); } invalidator.doneBuilding(); }); const pingIntervalTime = Math.max(1000, Math.min(5000, maxInactiveAge)); const disposeHandler = setInterval(function() { disposeInactiveEntries(watcher, lastClientAccessPages, maxInactiveAge); }, pingIntervalTime + 1000); disposeHandler.unref(); function handlePing(pg) { const page = (0, _normalizePagePath).normalizePathSep(pg); const pageKey = `client${page}`; const entryInfo = entries[pageKey]; let toSend; // If there's no entry, it may have been invalidated and needs to be re-built. if (!entryInfo) { // if (page !== lastEntry) client pings, but there's no entry for page return { invalid: true }; } // 404 is an on demand entry but when a new page is added we have to refresh the page if (page === '/_error') { toSend = { invalid: true }; } else { toSend = { success: true }; } // We don't need to maintain active state of anything other than BUILT entries if (entryInfo.status !== BUILT) return; // If there's an entryInfo if (!lastClientAccessPages.includes(pageKey)) { lastClientAccessPages.unshift(pageKey); // Maintain the buffer max length if (lastClientAccessPages.length > pagesBufferLength) { lastClientAccessPages.pop(); } } entryInfo.lastActiveTime = Date.now(); entryInfo.dispose = false; return toSend; } return { async ensurePage (page, clientOnly) { let normalizedPagePath; try { normalizedPagePath = (0, _normalizePagePath).normalizePagePath(page); } catch (err1) { console.error(err1); throw (0, _require).pageNotFoundError(page); } let pagePath = await (0, _findPageFile).findPageFile(pagesDir, normalizedPagePath, nextConfig.pageExtensions); // Default the /_error route to the Next.js provided default page if (page === '/_error' && pagePath === null) { pagePath = 'next/dist/pages/_error'; } if (pagePath === null) { throw (0, _require).pageNotFoundError(normalizedPagePath); } let bundlePath; let absolutePagePath; if (pagePath.startsWith('next/dist/pages/')) { bundlePath = page; absolutePagePath = require.resolve(pagePath); } else { let pageUrl = pagePath.replace(/\\/g, '/'); pageUrl = `${pageUrl[0] !== '/' ? '/' : ''}${pageUrl.replace(new RegExp(`\\.+(?:${nextConfig.pageExtensions.join('|')})$`), '').replace(/\/index$/, '')}`; pageUrl = pageUrl === '' ? '/' : pageUrl; const bundleFile = (0, _normalizePagePath).normalizePagePath(pageUrl); bundlePath = _path.posix.join('pages', bundleFile); absolutePagePath = (0, _path).join(pagesDir, pagePath); page = _path.posix.normalize(pageUrl); } const normalizedPage = (0, _normalizePagePath).normalizePathSep(page); const isMiddleware = normalizedPage.match(_constants.MIDDLEWARE_ROUTE); const isApiRoute = normalizedPage.match(_constants.API_ROUTE) && !isMiddleware; const pageRuntimeConfig = await (0, _entries).getPageRuntime(absolutePagePath, nextConfig); const isEdgeServer = pageRuntimeConfig === 'edge'; const isCustomError = (0, _utils).isCustomErrorPage(page); let entriesChanged = false; const addPageEntry = (type)=>{ return new Promise((resolve, reject)=>{ // Makes sure the page that is being kept in on-demand-entries matches the webpack output const pageKey = `${type}${page}`; const entryInfo = entries[pageKey]; if (entryInfo) { entryInfo.lastActiveTime = Date.now(); entryInfo.dispose = false; if (entryInfo.status === BUILT) { resolve(); return; } doneCallbacks.once(pageKey, handleCallback); return; } entriesChanged = true; entries[pageKey] = { bundlePath, absolutePagePath, status: ADDED, lastActiveTime: Date.now(), dispose: false }; doneCallbacks.once(pageKey, handleCallback); function handleCallback(err) { if (err) return reject(err); resolve(); } }); }; const isClientOrMiddleware = clientOnly || isMiddleware; const promise = isApiRoute ? addPageEntry('server') : isClientOrMiddleware ? addPageEntry('client') : Promise.all([ addPageEntry('client'), addPageEntry(isEdgeServer && !isCustomError ? 'edge-server' : 'server'), ]); if (entriesChanged) { (0, _output).reportTrigger(isApiRoute || isMiddleware || clientOnly ? normalizedPage : `${normalizedPage} (client and server)`); invalidator.invalidate(); } return promise; }, onHMR (client) { client.addEventListener('message', ({ data })=>{ data = typeof data !== 'string' ? data.toString() : data; try { const parsedData = JSON.parse(data); if (parsedData.event === 'ping') { const result = handlePing(parsedData.page); client.send(JSON.stringify({ ...result, event: 'pong' })); } } catch (_) {} }); } }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const ADDED = Symbol('added'); exports.ADDED = ADDED; const BUILDING = Symbol('building'); exports.BUILDING = BUILDING; const BUILT = Symbol('built'); exports.BUILT = BUILT; const entries = {}; exports.entries = entries; function disposeInactiveEntries(_watcher, lastClientAccessPages, maxInactiveAge) { Object.keys(entries).forEach((page)=>{ const { lastActiveTime , status , dispose } = entries[page]; // Skip pages already scheduled for disposing if (dispose) return; // This means this entry is currently building or just added // We don't need to dispose those entries. if (status !== BUILT) return; // We should not build the last accessed page even we didn't get any pings // Sometimes, it's possible our XHR ping to wait before completing other requests. // In that case, we should not dispose the current viewing page if (lastClientAccessPages.includes(page)) return; if (lastActiveTime && Date.now() - lastActiveTime > maxInactiveAge) { entries[page].dispose = true; } }); } // Make sure only one invalidation happens at a time // Otherwise, webpack hash gets changed and it'll force the client to reload. class Invalidator { constructor(watcher, multiCompiler){ this.multiCompiler = multiCompiler; this.watcher = watcher; // contains an array of types of compilers currently building this.building = false; this.rebuildAgain = false; } invalidate() { // If there's a current build is processing, we won't abort it by invalidating. // (If aborted, it'll cause a client side hard reload) // But let it to invalidate just after the completion. // So, it can re-build the queued pages at once. if (this.building) { this.rebuildAgain = true; return; } this.building = true; this.watcher.invalidate(); } startBuilding() { this.building = true; } doneBuilding() { this.building = false; if (this.rebuildAgain) { this.rebuildAgain = false; this.invalidate(); } } } //# sourceMappingURL=on-demand-entry-handler.js.map