docs: function match(routes, path) {
  
    // Get the query object before it gets stripped by the normalizer
    const query = getQuery(path);
    const pathname = path;
  
    // Normalize the path
    path = normalizePath(path);
  
    // Loop over each route to find the match
    for (let i = 0; i < routes.length; i++) {
  
      // Normalize the current route
      const currentRoute = normalizePath(routes[i].route);
  
      // Create the params object
      const params = {};
  
      // Check if it's a straight match first
      if(currentRoute === path) {
        return Object.assign({}, routes[i], {params, query, pathname, path});
      }
  
      // If there are no dynamic/optional/match-all parts then this route cannot match
      if(!isDynamicRoute.test(routes[i].route)) {
        continue;
      }
  
      // Split up the route by it's slashes so that we may match by section
      const routeSections = currentRoute.split('/');
      const pathSections = path.split('/');
  
      // Loop over each section looking for a full match
      for (let j = routeSections.length - 1; j >= 0; j--) {
  
        // If the route is to match everything, then return
        if(j === 0 && routeSections[j] === '*') {
          return Object.assign({}, routes[i], {params, query, pathname, path});
        }
  
        // If this section is optional
        if(optionalSectionRegex.test(routeSections[j])) {
  
          const currentSection = routeSections[j].replace(removeOptionalRegex, '');
  
          // If it's a param, add it to the params
          if(paramRegex.test(currentSection)) {
            params[currentSection.replace(paramRegex, '')] = pathSections[j];
            continue;
          }
  
          // If it's a star then skip to next
          if(currentSection === '*') {
            continue;
          }
  
          // If the optional section can possible be missing skip to next section
          if(routeSections.length === pathSections.length + 1) {
            continue;
          }
  
          // If the path and route sections have same number of sections and this does match, skip to next
          if(routeSections.length === pathSections.length && currentSection === pathSections[j]) {
            continue;
          }
  
          // Reject this route as the possible match to the path
          break;
        }
  
        // If it's a param, add it to the params
        if(paramRegex.test(routeSections[j])) {
          params[routeSections[j].replace(paramRegex, '')] = pathSections[j];
  
          if(j === 0) {
            return Object.assign({}, routes[i], {params, query, pathname, path});
          }
  
          continue;
        }
  
        // If it's a star then skip to next
        if(routeSections[j] === '*') {
          continue;
        }
  
        // If this doesn't match then go to next route
        if(routeSections[j] !== pathSections[j]) {
          break;
        }
  
        // If the last item matches strictly then return that match
        if(j === 0 && routeSections[j] === pathSections[j]) {
          return Object.assign({}, routes[i], {params, query, pathname, path});
        }
      }
    }
  
    // No match found
    return {path: '', params: {}, query};
  }