UNPKG

@stencil/router

Version:
280 lines (275 loc) 9.03 kB
'use strict'; const __chunk_4 = require('./chunk-e6311a56.js'); /** * TS adaption of https://github.com/pillarjs/path-to-regexp/blob/master/index.js */ /** * Default configs. */ const DEFAULT_DELIMITER = '/'; const DEFAULT_DELIMITERS = './'; /** * The main path matching regexp utility. */ const PATH_REGEXP = new RegExp([ // Match escaped characters that would otherwise appear in future matches. // This allows the user to escape special characters that won't transform. '(\\\\.)', // Match Express-style parameters and un-named parameters with a prefix // and optional suffixes. Matches appear as: // // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"] // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined] '(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?' ].join('|'), 'g'); /** * Parse a string for the raw tokens. */ const parse = (str, options) => { var tokens = []; var key = 0; var index = 0; var path = ''; var defaultDelimiter = (options && options.delimiter) || DEFAULT_DELIMITER; var delimiters = (options && options.delimiters) || DEFAULT_DELIMITERS; var pathEscaped = false; var res; while ((res = PATH_REGEXP.exec(str)) !== null) { var m = res[0]; var escaped = res[1]; var offset = res.index; path += str.slice(index, offset); index = offset + m.length; // Ignore already escaped sequences. if (escaped) { path += escaped[1]; pathEscaped = true; continue; } var prev = ''; var next = str[index]; var name = res[2]; var capture = res[3]; var group = res[4]; var modifier = res[5]; if (!pathEscaped && path.length) { var k = path.length - 1; if (delimiters.indexOf(path[k]) > -1) { prev = path[k]; path = path.slice(0, k); } } // Push the current path onto the tokens. if (path) { tokens.push(path); path = ''; pathEscaped = false; } var partial = prev !== '' && next !== undefined && next !== prev; var repeat = modifier === '+' || modifier === '*'; var optional = modifier === '?' || modifier === '*'; var delimiter = prev || defaultDelimiter; var pattern = capture || group; tokens.push({ name: name || key++, prefix: prev, delimiter: delimiter, optional: optional, repeat: repeat, partial: partial, pattern: pattern ? escapeGroup(pattern) : '[^' + escapeString(delimiter) + ']+?' }); } // Push any remaining characters. if (path || index < str.length) { tokens.push(path + str.substr(index)); } return tokens; }; /** * Escape a regular expression string. */ const escapeString = (str) => { return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1'); }; /** * Escape the capturing group by escaping special characters and meaning. */ const escapeGroup = (group) => { return group.replace(/([=!:$/()])/g, '\\$1'); }; /** * Get the flags for a regexp from the options. */ const flags = (options) => { return options && options.sensitive ? '' : 'i'; }; /** * Pull out keys from a regexp. */ const regexpToRegexp = (path, keys) => { if (!keys) return path; // Use a negative lookahead to match only capturing groups. var groups = path.source.match(/\((?!\?)/g); if (groups) { for (var i = 0; i < groups.length; i++) { keys.push({ name: i, prefix: null, delimiter: null, optional: false, repeat: false, partial: false, pattern: null }); } } return path; }; /** * Transform an array into a regexp. */ const arrayToRegexp = (path, keys, options) => { var parts = []; for (var i = 0; i < path.length; i++) { parts.push(pathToRegexp(path[i], keys, options).source); } return new RegExp('(?:' + parts.join('|') + ')', flags(options)); }; /** * Create a path regexp from string input. */ const stringToRegexp = (path, keys, options) => { return tokensToRegExp(parse(path, options), keys, options); }; /** * Expose a function for taking tokens and returning a RegExp. */ const tokensToRegExp = (tokens, keys, options) => { options = options || {}; var strict = options.strict; var end = options.end !== false; var delimiter = escapeString(options.delimiter || DEFAULT_DELIMITER); var delimiters = options.delimiters || DEFAULT_DELIMITERS; var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|'); var route = ''; var isEndDelimited = false; // Iterate over the tokens and create our regexp string. for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (typeof token === 'string') { route += escapeString(token); isEndDelimited = i === tokens.length - 1 && delimiters.indexOf(token[token.length - 1]) > -1; } else { var prefix = escapeString(token.prefix || ''); var capture = token.repeat ? '(?:' + token.pattern + ')(?:' + prefix + '(?:' + token.pattern + '))*' : token.pattern; if (keys) keys.push(token); if (token.optional) { if (token.partial) { route += prefix + '(' + capture + ')?'; } else { route += '(?:' + prefix + '(' + capture + '))?'; } } else { route += prefix + '(' + capture + ')'; } } } if (end) { if (!strict) route += '(?:' + delimiter + ')?'; route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'; } else { if (!strict) route += '(?:' + delimiter + '(?=' + endsWith + '))?'; if (!isEndDelimited) route += '(?=' + delimiter + '|' + endsWith + ')'; } return new RegExp('^' + route, flags(options)); }; /** * Normalize the given path string, returning a regular expression. * * An empty array can be passed in for the keys, which will hold the * placeholder key descriptions. For example, using `/user/:id`, `keys` will * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. */ const pathToRegexp = (path, keys, options) => { if (path instanceof RegExp) { return regexpToRegexp(path, keys); } if (Array.isArray(path)) { return arrayToRegexp(path, keys, options); } return stringToRegexp(path, keys, options); }; let cacheCount = 0; const patternCache = {}; const cacheLimit = 10000; // Memoized function for creating the path match regex const compilePath = (pattern, options) => { const cacheKey = `${options.end}${options.strict}`; const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {}); const cachePattern = JSON.stringify(pattern); if (cache[cachePattern]) { return cache[cachePattern]; } const keys = []; const re = pathToRegexp(pattern, keys, options); const compiledPattern = { re, keys }; if (cacheCount < cacheLimit) { cache[cachePattern] = compiledPattern; cacheCount += 1; } return compiledPattern; }; /** * Public API for matching a URL pathname to a path pattern. */ const matchPath = (pathname, options = {}) => { if (typeof options === 'string') { options = { path: options }; } const { path = '/', exact = false, strict = false } = options; const { re, keys } = compilePath(path, { end: exact, strict }); const match = re.exec(pathname); if (!match) { return null; } const [url, ...values] = match; const isExact = pathname === url; if (exact && !isExact) { return null; } return { path, url: path === '/' && url === '' ? '/' : url, isExact, params: keys.reduce((memo, key, index) => { memo[key.name] = values[index]; return memo; }, {}) }; }; const matchesAreEqual = (a, b) => { if (a == null && b == null) { return true; } if (b == null) { return false; } return a && b && a.path === b.path && a.url === b.url && __chunk_4.valueEqual(a.params, b.params); }; exports.matchPath = matchPath; exports.matchesAreEqual = matchesAreEqual;