UNPKG

next

Version:

The React Framework

174 lines (173 loc) • 6.94 kB
import { setGlobal } from '../../trace'; import * as Log from '../output/log'; import * as path from 'node:path'; import loadConfig from '../../server/config'; import { PHASE_ANALYZE } from '../../shared/lib/constants'; import { turbopackAnalyze } from '../turbopack-analyze'; import { durationToString } from '../duration-to-string'; import { cp, writeFile, mkdir } from 'node:fs/promises'; import { discoverRoutes } from '../route-discovery'; import { findPagesDir } from '../../lib/find-pages-dir'; import loadCustomRoutes from '../../lib/load-custom-routes'; import { generateRoutesManifest } from '../generate-routes-manifest'; import { checkIsAppPPREnabled } from '../../server/lib/experimental/ppr'; import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'; import http from 'node:http'; // @ts-expect-error types are in @types/serve-handler import serveHandler from 'next/dist/compiled/serve-handler'; import { Telemetry } from '../../telemetry/storage'; import { eventAnalyzeCompleted } from '../../telemetry/events'; import { traceGlobals } from '../../trace/shared'; import { Bundler } from '../../lib/bundler'; export default async function analyze({ dir, reactProductionProfiling = false, noMangling = false, appDirOnly = false, output = false, port = 4000 }) { try { const config = await loadConfig(PHASE_ANALYZE, dir, { silent: false, reactProductionProfiling, bundler: Bundler.Turbopack }); process.env.NEXT_DEPLOYMENT_ID = config.deploymentId || ''; const distDir = path.join(dir, '.next'); const telemetry = new Telemetry({ distDir }); setGlobal('phase', PHASE_ANALYZE); setGlobal('distDir', distDir); setGlobal('telemetry', telemetry); Log.info('Analyzing a production build...'); const analyzeContext = { config, dir, distDir, noMangling, appDirOnly }; const { duration: analyzeDuration, shutdownPromise } = await turbopackAnalyze(analyzeContext); const durationString = durationToString(analyzeDuration); const analyzeDir = path.join(distDir, 'diagnostics/analyze'); await shutdownPromise; const routes = await collectRoutesForAnalyze(dir, config, appDirOnly); await cp(path.join(__dirname, '../../bundle-analyzer'), analyzeDir, { recursive: true }); await mkdir(path.join(analyzeDir, 'data'), { recursive: true }); await writeFile(path.join(analyzeDir, 'data', 'routes.json'), JSON.stringify(routes, null, 2)); let logMessage = `Analyze completed in ${durationString}.`; if (output) { logMessage += ` Results written to ${analyzeDir}.\nTo explore the analyze results interactively, run \`next experimental-analyze\` without \`--output\`.`; } Log.event(logMessage); telemetry.record(eventAnalyzeCompleted({ success: true, durationInSeconds: Math.round(analyzeDuration), totalPageCount: routes.length })); if (!output) { await startServer(analyzeDir, port); } } catch (e) { const telemetry = traceGlobals.get('telemetry'); if (telemetry) { telemetry.record(eventAnalyzeCompleted({ success: false })); } throw e; } } /** * Collects all routes from the project for the bundle analyzer. * Returns a list of route paths (both static and dynamic). */ async function collectRoutesForAnalyze(dir, config, appDirOnly) { const { pagesDir, appDir } = findPagesDir(dir); let appType; if (pagesDir && appDir) { appType = 'hybrid'; } else if (pagesDir) { appType = 'pages'; } else if (appDir) { appType = 'app'; } else { throw Object.defineProperty(new Error('No pages or app directory found.'), "__NEXT_ERROR_CODE", { value: "E929", enumerable: false, configurable: true }); } const discovery = await discoverRoutes({ appDir, pagesDir, pageExtensions: config.pageExtensions, isDev: false, baseDir: dir, isSrcDir: path.relative(dir, pagesDir || appDir || '').startsWith('src'), appDirOnly }); const pageKeys = { pages: Object.keys(discovery.mappedPages || {}), app: discovery.mappedAppPages ? Object.keys(discovery.mappedAppPages).map((key)=>normalizeAppPath(key)) : [] }; // Load custom routes const { redirects, headers, onMatchHeaders, rewrites } = await loadCustomRoutes(config); // Compute restricted redirect paths const restrictedRedirectPaths = [ '/_next' ].map((pathPrefix)=>config.basePath ? `${config.basePath}${pathPrefix}` : pathPrefix); const isAppPPREnabled = checkIsAppPPREnabled(config.experimental.ppr); // Generate routes manifest const { routesManifest } = generateRoutesManifest({ appType, pageKeys, config, redirects, headers, onMatchHeaders, rewrites, restrictedRedirectPaths, isAppPPREnabled }); return routesManifest.dynamicRoutes.map((r)=>r.page).concat(routesManifest.staticRoutes.map((r)=>r.page)); } function startServer(dir, port) { const server = http.createServer((req, res)=>{ return serveHandler(req, res, { public: dir }); }); return new Promise((resolve, reject)=>{ function onError(err) { server.close(()=>{ reject(err); }); } server.on('error', onError); server.listen(port, 'localhost', ()=>{ const address = server.address(); if (address == null) { reject(Object.defineProperty(new Error('Unable to get server address'), "__NEXT_ERROR_CODE", { value: "E928", enumerable: false, configurable: true })); return; } // No longer needed after startup server.removeListener('error', onError); let addressString; if (typeof address === 'string') { addressString = address; } else if (address.family === 'IPv6' && (address.address === '::' || address.address === '::1')) { addressString = `localhost:${address.port}`; } else if (address.family === 'IPv6') { addressString = `[${address.address}]:${address.port}`; } else { addressString = `${address.address}:${address.port}`; } Log.info(`Bundle analyzer available at http://${addressString}`); resolve(); }); }); } //# sourceMappingURL=index.js.map