UNPKG

15.3 kBJavaScriptView Raw
1"use strict";exports.__esModule=true;exports.renderScriptError=renderScriptError;exports.default=void 0;var _middleware=require("@next/react-dev-overlay/lib/middleware");var _hotMiddleware=require("./hot-middleware");var _path=require("path");var _webpack=require("next/dist/compiled/webpack/webpack");var _entries=require("../build/entries");var _output=require("../build/output");var _webpackConfig=_interopRequireDefault(require("../build/webpack-config"));var _constants=require("../lib/constants");var _recursiveDelete=require("../lib/recursive-delete");var _constants2=require("../next-server/lib/constants");var _router=require("../next-server/server/router");var _findPageFile=require("./lib/find-page-file");var _onDemandEntryHandler=_interopRequireWildcard(require("./on-demand-entry-handler"));var _normalizePagePath=require("../next-server/server/normalize-page-path");var _getRouteFromEntrypoint=_interopRequireDefault(require("../next-server/server/get-route-from-entrypoint"));var _isWriteable=require("../build/is-writeable");var _querystring=require("querystring");var _utils=require("../build/utils");function _getRequireWildcardCache(){if(typeof WeakMap!=="function")return null;var cache=new WeakMap();_getRequireWildcardCache=function(){return cache;};return cache;}function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}if(obj===null||typeof obj!=="object"&&typeof obj!=="function"){return{default:obj};}var cache=_getRequireWildcardCache();if(cache&&cache.has(obj)){return cache.get(obj);}var newObj={};var hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;if(desc&&(desc.get||desc.set)){Object.defineProperty(newObj,key,desc);}else{newObj[key]=obj[key];}}}newObj.default=obj;if(cache){cache.set(obj,newObj);}return newObj;}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}async function renderScriptError(res,error,{verbose=true}={}){// Asks CDNs and others to not to cache the errored page
2res.setHeader('Cache-Control','no-cache, no-store, max-age=0, must-revalidate');if(error.code==='ENOENT'){res.statusCode=404;res.end('404 - Not Found');return;}if(verbose){console.error(error.stack);}res.statusCode=500;res.end('500 - Internal Error');}function addCorsSupport(req,res){const isApiRoute=req.url.match(_constants.API_ROUTE);// API routes handle their own CORS headers
3if(isApiRoute){return{preflight:false};}if(!req.headers.origin){return{preflight:false};}res.setHeader('Access-Control-Allow-Origin',req.headers.origin);res.setHeader('Access-Control-Allow-Methods','OPTIONS, GET');// Based on https://github.com/primus/access-control/blob/4cf1bc0e54b086c91e6aa44fb14966fa5ef7549c/index.js#L158
4if(req.headers['access-control-request-headers']){res.setHeader('Access-Control-Allow-Headers',req.headers['access-control-request-headers']);}if(req.method==='OPTIONS'){res.writeHead(200);res.end();return{preflight:true};}return{preflight:false};}const matchNextPageBundleRequest=(0,_router.route)('/_next/static/chunks/pages/:path*.js(\\.map|)');// Recursively look up the issuer till it ends up at the root
5function findEntryModule(issuer){if(issuer.issuer){return findEntryModule(issuer.issuer);}return issuer;}function erroredPages(compilation){const failedPages={};for(const error of compilation.errors){if(!error.origin){continue;}const entryModule=findEntryModule(error.origin);const{name}=entryModule;if(!name){continue;}// Only pages have to be reloaded
6const enhancedName=(0,_getRouteFromEntrypoint.default)(name);if(!enhancedName){continue;}if(!failedPages[enhancedName]){failedPages[enhancedName]=[];}failedPages[enhancedName].push(error);}return failedPages;}class HotReloader{constructor(dir,{config,pagesDir,buildId,previewProps,rewrites}){this.dir=void 0;this.buildId=void 0;this.middlewares=void 0;this.pagesDir=void 0;this.webpackHotMiddleware=void 0;this.config=void 0;this.stats=void 0;this.serverStats=void 0;this.clientError=null;this.serverError=null;this.serverPrevDocumentHash=void 0;this.prevChunkNames=void 0;this.onDemandEntries=void 0;this.previewProps=void 0;this.watcher=void 0;this.rewrites=void 0;this.fallbackWatcher=void 0;this.isWebpack5=void 0;this.buildId=buildId;this.dir=dir;this.middlewares=[];this.pagesDir=pagesDir;this.webpackHotMiddleware=null;this.stats=null;this.serverStats=null;this.serverPrevDocumentHash=null;this.config=config;this.previewProps=previewProps;this.rewrites=rewrites;this.isWebpack5=_webpack.isWebpack5;}async run(req,res,parsedUrl){// Usually CORS support is not needed for the hot-reloader (this is dev only feature)
7// With when the app runs for multi-zones support behind a proxy,
8// the current page is trying to access this URL via assetPrefix.
9// That's when the CORS support is needed.
10const{preflight}=addCorsSupport(req,res);if(preflight){return{};}// When a request comes in that is a page bundle, e.g. /_next/static/<buildid>/pages/index.js
11// we have to compile the page using on-demand-entries, this middleware will handle doing that
12// by adding the page to on-demand-entries, waiting till it's done
13// and then the bundle will be served like usual by the actual route in server/index.js
14const handlePageBundleRequest=async(pageBundleRes,parsedPageBundleUrl)=>{const{pathname}=parsedPageBundleUrl;const params=matchNextPageBundleRequest(pathname);if(!params){return{};}let decodedPagePath;try{decodedPagePath=`/${params.path.map(param=>decodeURIComponent(param)).join('/')}`;}catch(_){const err=new Error('failed to decode param');err.code='DECODE_FAILED';throw err;}const page=(0,_normalizePagePath.denormalizePagePath)(decodedPagePath);if(page==='/_error'||_constants2.BLOCKED_PAGES.indexOf(page)===-1){try{await this.ensurePage(page);}catch(error){await renderScriptError(pageBundleRes,error);return{finished:true};}const errors=await this.getCompilationErrors(page);if(errors.length>0){await renderScriptError(pageBundleRes,errors[0],{verbose:false});return{finished:true};}}return{};};const{finished}=await handlePageBundleRequest(res,parsedUrl);for(const fn of this.middlewares){await new Promise((resolve,reject)=>{fn(req,res,err=>{if(err)return reject(err);resolve();});});}return{finished};}async clean(){return(0,_recursiveDelete.recursiveDelete)((0,_path.join)(this.dir,this.config.distDir),/^cache/);}async getWebpackConfig(){const pagePaths=await Promise.all([(0,_findPageFile.findPageFile)(this.pagesDir,'/_app',this.config.pageExtensions),(0,_findPageFile.findPageFile)(this.pagesDir,'/_document',this.config.pageExtensions)]);const pages=(0,_entries.createPagesMapping)(pagePaths.filter(i=>i!==null),this.config.pageExtensions);const entrypoints=(0,_entries.createEntrypoints)(pages,'server',this.buildId,this.previewProps,this.config,[]);return Promise.all([(0,_webpackConfig.default)(this.dir,{dev:true,isServer:false,config:this.config,buildId:this.buildId,pagesDir:this.pagesDir,rewrites:this.rewrites,entrypoints:entrypoints.client}),(0,_webpackConfig.default)(this.dir,{dev:true,isServer:true,config:this.config,buildId:this.buildId,pagesDir:this.pagesDir,rewrites:this.rewrites,entrypoints:entrypoints.server})]);}async buildFallbackError(){if(this.fallbackWatcher)return;const fallbackConfig=await(0,_webpackConfig.default)(this.dir,{dev:true,isServer:false,config:this.config,buildId:this.buildId,pagesDir:this.pagesDir,rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},isDevFallback:true,entrypoints:(0,_entries.createEntrypoints)({'/_app':'next/dist/pages/_app','/_error':'next/dist/pages/_error'},'server',this.buildId,this.previewProps,this.config,[]).client});const fallbackCompiler=(0,_webpack.webpack)(fallbackConfig);this.fallbackWatcher=await new Promise(resolve=>{let bootedFallbackCompiler=false;fallbackCompiler.watch(// @ts-ignore webpack supports an array of watchOptions when using a multiCompiler
15fallbackConfig.watchOptions,// Errors are handled separately
16_err=>{if(!bootedFallbackCompiler){bootedFallbackCompiler=true;resolve(true);}});});}async start(){await this.clean();const configs=await this.getWebpackConfig();for(const config of configs){const defaultEntry=config.entry;config.entry=async(...args)=>{// @ts-ignore entry is always a functon
17const entrypoints=await defaultEntry(...args);const isClientCompilation=config.name==='client';await Promise.all(Object.keys(_onDemandEntryHandler.entries).map(async page=>{if(isClientCompilation&&page.match(_constants.API_ROUTE)){return;}const{serverBundlePath,clientBundlePath,absolutePagePath}=_onDemandEntryHandler.entries[page];const pageExists=await(0,_isWriteable.isWriteable)(absolutePagePath);if(!pageExists){// page was removed
18delete _onDemandEntryHandler.entries[page];return;}_onDemandEntryHandler.entries[page].status=_onDemandEntryHandler.BUILDING;const pageLoaderOpts={page,absolutePagePath};entrypoints[isClientCompilation?clientBundlePath:serverBundlePath]=isClientCompilation?`next-client-pages-loader?${(0,_querystring.stringify)(pageLoaderOpts)}!`:absolutePagePath;}));return entrypoints;};}const multiCompiler=(0,_webpack.webpack)(configs);(0,_output.watchCompilers)(multiCompiler.compilers[0],multiCompiler.compilers[1]);// Watch for changes to client/server page files so we can tell when just
19// the server file changes and trigger a reload for GS(S)P pages
20const changedClientPages=new Set();const changedServerPages=new Set();const prevClientPageHashes=new Map();const prevServerPageHashes=new Map();const trackPageChanges=(pageHashMap,changedItems)=>stats=>{stats.entrypoints.forEach((entry,key)=>{if(key.startsWith('pages/')){entry.chunks.forEach(chunk=>{if(chunk.id===key){const prevHash=pageHashMap.get(key);if(prevHash&&prevHash!==chunk.hash){changedItems.add(key);}pageHashMap.set(key,chunk.hash);}});}});};multiCompiler.compilers[0].hooks.emit.tap('NextjsHotReloaderForClient',trackPageChanges(prevClientPageHashes,changedClientPages));multiCompiler.compilers[1].hooks.emit.tap('NextjsHotReloaderForServer',trackPageChanges(prevServerPageHashes,changedServerPages));// This plugin watches for changes to _document.js and notifies the client side that it should reload the page
21multiCompiler.compilers[1].hooks.failed.tap('NextjsHotReloaderForServer',err=>{this.serverError=err;this.serverStats=null;});multiCompiler.compilers[1].hooks.done.tap('NextjsHotReloaderForServer',stats=>{this.serverError=null;this.serverStats=stats;const serverOnlyChanges=(0,_utils.difference)(changedServerPages,changedClientPages);changedClientPages.clear();changedServerPages.clear();if(serverOnlyChanges.length>0){this.send({event:'serverOnlyChanges',pages:serverOnlyChanges.map(pg=>(0,_normalizePagePath.denormalizePagePath)(pg.substr('pages'.length)))});}const{compilation}=stats;// We only watch `_document` for changes on the server compilation
22// the rest of the files will be triggered by the client compilation
23const documentChunk=compilation.namedChunks.get('pages/_document');// If the document chunk can't be found we do nothing
24if(!documentChunk){console.warn('_document.js chunk not found');return;}// Initial value
25if(this.serverPrevDocumentHash===null){this.serverPrevDocumentHash=documentChunk.hash;return;}// If _document.js didn't change we don't trigger a reload
26if(documentChunk.hash===this.serverPrevDocumentHash){return;}// Notify reload to reload the page, as _document.js was changed (different hash)
27this.send('reloadPage');this.serverPrevDocumentHash=documentChunk.hash;});multiCompiler.compilers[0].hooks.failed.tap('NextjsHotReloaderForClient',err=>{this.clientError=err;this.stats=null;});multiCompiler.compilers[0].hooks.done.tap('NextjsHotReloaderForClient',stats=>{this.clientError=null;this.stats=stats;const{compilation}=stats;const chunkNames=new Set([...compilation.namedChunks.keys()].filter(name=>!!(0,_getRouteFromEntrypoint.default)(name)));if(this.prevChunkNames){// detect chunks which have to be replaced with a new template
28// e.g, pages/index.js <-> pages/_error.js
29const addedPages=diff(chunkNames,this.prevChunkNames);const removedPages=diff(this.prevChunkNames,chunkNames);if(addedPages.size>0){for(const addedPage of addedPages){const page=(0,_getRouteFromEntrypoint.default)(addedPage);this.send('addedPage',page);}}if(removedPages.size>0){for(const removedPage of removedPages){const page=(0,_getRouteFromEntrypoint.default)(removedPage);this.send('removedPage',page);}}}this.prevChunkNames=chunkNames;});this.webpackHotMiddleware=new _hotMiddleware.WebpackHotMiddleware(multiCompiler.compilers);let booted=false;this.watcher=await new Promise(resolve=>{const watcher=multiCompiler.watch(// @ts-ignore webpack supports an array of watchOptions when using a multiCompiler
30configs.map(config=>config.watchOptions),// Errors are handled separately
31_err=>{if(!booted){booted=true;resolve(watcher);}});});this.onDemandEntries=(0,_onDemandEntryHandler.default)(this.watcher,multiCompiler,{pagesDir:this.pagesDir,pageExtensions:this.config.pageExtensions,...this.config.onDemandEntries});this.middlewares=[// must come before hotMiddleware
32this.onDemandEntries.middleware,this.webpackHotMiddleware.middleware,(0,_middleware.getOverlayMiddleware)({isWebpack5:_webpack.isWebpack5,rootDirectory:this.dir,stats:()=>this.stats,serverStats:()=>this.serverStats})];}async stop(){await new Promise((resolve,reject)=>{this.watcher.close(err=>err?reject(err):resolve(true));});if(this.fallbackWatcher){await new Promise((resolve,reject)=>{this.fallbackWatcher.close(err=>err?reject(err):resolve(true));});}}async getCompilationErrors(page){var _this$stats,_this$serverStats;const normalizedPage=(0,_normalizePagePath.normalizePathSep)(page);if(this.clientError||this.serverError){return[this.clientError||this.serverError];}else if((_this$stats=this.stats)!=null&&_this$stats.hasErrors()){const{compilation}=this.stats;const failedPages=erroredPages(compilation);// If there is an error related to the requesting page we display it instead of the first error
33if(failedPages[normalizedPage]&&failedPages[normalizedPage].length>0){return failedPages[normalizedPage];}// If none were found we still have to show the other errors
34return this.stats.compilation.errors;}else if((_this$serverStats=this.serverStats)!=null&&_this$serverStats.hasErrors()){const{compilation}=this.serverStats;const failedPages=erroredPages(compilation);// If there is an error related to the requesting page we display it instead of the first error
35if(failedPages[normalizedPage]&&failedPages[normalizedPage].length>0){return failedPages[normalizedPage];}// If none were found we still have to show the other errors
36return this.serverStats.compilation.errors;}return[];}send(action,...args){this.webpackHotMiddleware.publish(action&&typeof action==='object'?action:{action,data:args});}async ensurePage(page){// Make sure we don't re-build or dispose prebuilt pages
37if(page!=='/_error'&&_constants2.BLOCKED_PAGES.indexOf(page)!==-1){return;}if(this.serverError||this.clientError){return Promise.reject(this.serverError||this.clientError);}return this.onDemandEntries.ensurePage(page);}}exports.default=HotReloader;function diff(a,b){return new Set([...a].filter(v=>!b.has(v)));}
38//# sourceMappingURL=hot-reloader.js.map
\No newline at end of file