UNPKG

next

Version:

The React Framework

244 lines (243 loc) 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { getSortedRouteObjects: null, getSortedRoutes: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { getSortedRouteObjects: function() { return getSortedRouteObjects; }, getSortedRoutes: function() { return getSortedRoutes; } }); class UrlNode { insert(urlPath) { this._insert(urlPath.split('/').filter(Boolean), [], false); } smoosh() { return this._smoosh(); } _smoosh(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; } } 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(); } 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