UNPKG

next

Version:

The React Framework

223 lines (222 loc) 11.2 kB
class UrlNode { insert(urlPath) { this._insert(urlPath.split('/').filter(Boolean), [], false); } smoosh() { return this._smoosh(); } _smoosh(prefix) { if (prefix === void 0) prefix = '/'; const childrenPaths = [ ...this.children.keys() ].sort(); if (this.slugName !== null) { childrenPaths.splice(childrenPaths.indexOf('[]'), 1); } if (this.restSlugName !== null) { childrenPaths.splice(childrenPaths.indexOf('[...]'), 1); } if (this.optionalRestSlugName !== null) { childrenPaths.splice(childrenPaths.indexOf('[[...]]'), 1); } const routes = childrenPaths.map((c)=>this.children.get(c)._smoosh("" + prefix + c + "/")).reduce((prev, curr)=>[ ...prev, ...curr ], []); if (this.slugName !== null) { routes.push(...this.children.get('[]')._smoosh(prefix + "[" + this.slugName + "]/")); } if (!this.placeholder) { const r = prefix === '/' ? '/' : prefix.slice(0, -1); if (this.optionalRestSlugName != null) { throw Object.defineProperty(new Error('You cannot define a route with the same specificity as a optional catch-all route ("' + r + '" and "' + r + "[[..." + this.optionalRestSlugName + ']]").'), "__NEXT_ERROR_CODE", { value: "E458", enumerable: false, configurable: true }); } routes.unshift(r); } if (this.restSlugName !== null) { routes.push(...this.children.get('[...]')._smoosh(prefix + "[..." + this.restSlugName + "]/")); } if (this.optionalRestSlugName !== null) { routes.push(...this.children.get('[[...]]')._smoosh(prefix + "[[..." + this.optionalRestSlugName + "]]/")); } return routes; } _insert(urlPaths, slugNames, isCatchAll) { if (urlPaths.length === 0) { this.placeholder = false; return; } if (isCatchAll) { throw Object.defineProperty(new Error("Catch-all must be the last part of the URL."), "__NEXT_ERROR_CODE", { value: "E392", enumerable: false, configurable: true }); } // The next segment in the urlPaths list let nextSegment = urlPaths[0]; // Check if the segment matches `[something]` if (nextSegment.startsWith('[') && nextSegment.endsWith(']')) { // Strip `[` and `]`, leaving only `something` let segmentName = nextSegment.slice(1, -1); let isOptional = false; if (segmentName.startsWith('[') && segmentName.endsWith(']')) { // Strip optional `[` and `]`, leaving only `something` segmentName = segmentName.slice(1, -1); isOptional = true; } if (segmentName.startsWith('…')) { throw Object.defineProperty(new Error("Detected a three-dot character ('…') at ('" + segmentName + "'). Did you mean ('...')?"), "__NEXT_ERROR_CODE", { value: "E147", enumerable: false, configurable: true }); } if (segmentName.startsWith('...')) { // Strip `...`, leaving only `something` segmentName = segmentName.substring(3); isCatchAll = true; } if (segmentName.startsWith('[') || segmentName.endsWith(']')) { throw Object.defineProperty(new Error("Segment names may not start or end with extra brackets ('" + segmentName + "')."), "__NEXT_ERROR_CODE", { value: "E421", enumerable: false, configurable: true }); } if (segmentName.startsWith('.')) { throw Object.defineProperty(new Error("Segment names may not start with erroneous periods ('" + segmentName + "')."), "__NEXT_ERROR_CODE", { value: "E288", enumerable: false, configurable: true }); } function handleSlug(previousSlug, nextSlug) { if (previousSlug !== null) { // If the specific segment already has a slug but the slug is not `something` // This prevents collisions like: // pages/[post]/index.js // pages/[id]/index.js // Because currently multiple dynamic params on the same segment level are not supported if (previousSlug !== nextSlug) { // TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment. throw Object.defineProperty(new Error("You cannot use different slug names for the same dynamic path ('" + previousSlug + "' !== '" + nextSlug + "')."), "__NEXT_ERROR_CODE", { value: "E337", enumerable: false, configurable: true }); } } slugNames.forEach((slug)=>{ if (slug === nextSlug) { throw Object.defineProperty(new Error('You cannot have the same slug name "' + nextSlug + '" repeat within a single dynamic path'), "__NEXT_ERROR_CODE", { value: "E247", enumerable: false, configurable: true }); } if (slug.replace(/\W/g, '') === nextSegment.replace(/\W/g, '')) { throw Object.defineProperty(new Error('You cannot have the slug names "' + slug + '" and "' + nextSlug + '" differ only by non-word symbols within a single dynamic path'), "__NEXT_ERROR_CODE", { value: "E499", enumerable: false, configurable: true }); } }); slugNames.push(nextSlug); } if (isCatchAll) { if (isOptional) { if (this.restSlugName != null) { throw Object.defineProperty(new Error('You cannot use both an required and optional catch-all route at the same level ("[...' + this.restSlugName + ']" and "' + urlPaths[0] + '" ).'), "__NEXT_ERROR_CODE", { value: "E299", enumerable: false, configurable: true }); } handleSlug(this.optionalRestSlugName, segmentName); // slugName is kept as it can only be one particular slugName this.optionalRestSlugName = segmentName; // nextSegment is overwritten to [[...]] so that it can later be sorted specifically nextSegment = '[[...]]'; } else { if (this.optionalRestSlugName != null) { throw Object.defineProperty(new Error('You cannot use both an optional and required catch-all route at the same level ("[[...' + this.optionalRestSlugName + ']]" and "' + urlPaths[0] + '").'), "__NEXT_ERROR_CODE", { value: "E300", enumerable: false, configurable: true }); } handleSlug(this.restSlugName, segmentName); // slugName is kept as it can only be one particular slugName this.restSlugName = segmentName; // nextSegment is overwritten to [...] so that it can later be sorted specifically nextSegment = '[...]'; } } else { if (isOptional) { throw Object.defineProperty(new Error('Optional route parameters are not yet supported ("' + urlPaths[0] + '").'), "__NEXT_ERROR_CODE", { value: "E435", enumerable: false, configurable: true }); } handleSlug(this.slugName, segmentName); // slugName is kept as it can only be one particular slugName this.slugName = segmentName; // nextSegment is overwritten to [] so that it can later be sorted specifically nextSegment = '[]'; } } // If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode if (!this.children.has(nextSegment)) { this.children.set(nextSegment, new UrlNode()); } this.children.get(nextSegment)._insert(urlPaths.slice(1), slugNames, isCatchAll); } constructor(){ this.placeholder = true; this.children = new Map(); this.slugName = null; this.restSlugName = null; this.optionalRestSlugName = null; } } export function getSortedRoutes(normalizedPages) { // First the UrlNode is created, and every UrlNode can have only 1 dynamic segment // Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js // Only 1 dynamic segment per nesting level // So in the case that is test/integration/dynamic-routing it'll be this: // pages/[post]/comments.js // pages/blog/[post]/comment/[id].js // Both are fine because `pages/[post]` and `pages/blog` are on the same level // So in this case `UrlNode` created here has `this.slugName === 'post'` // And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities // Instead what has to be passed through is the upwards path's dynamic names const root = new UrlNode(); // Here the `root` gets injected multiple paths, and insert will break them up into sublevels normalizedPages.forEach((pagePath)=>root.insert(pagePath)); // Smoosh will then sort those sublevels up to the point where you get the correct route definition priority return root.smoosh(); } export function getSortedRouteObjects(objects, getter) { // We're assuming here that all the pathnames are unique, that way we can // sort the list and use the index as the key. const indexes = {}; const pathnames = []; for(let i = 0; i < objects.length; i++){ const pathname = getter(objects[i]); indexes[pathname] = i; pathnames[i] = pathname; } // Sort the pathnames. const sorted = getSortedRoutes(pathnames); // Map the sorted pathnames back to the original objects using the new sorted // index. return sorted.map((pathname)=>objects[indexes[pathname]]); } //# sourceMappingURL=sorted-routes.js.map