get-canonical-url
Version:
🔗 Determines the current page's canonical URL and optionally normalizes it for consistency.
1 lines • 14.8 kB
Source Map (JSON)
{"version":3,"file":"get-canonical-url.cjs","sources":["../node_modules/normalize-url/index.js","../src/get-canonical-url.ts"],"sourcesContent":["// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs\nconst DATA_URL_DEFAULT_MIME_TYPE = 'text/plain';\nconst DATA_URL_DEFAULT_CHARSET = 'us-ascii';\n\nconst testParameter = (name, filters) => filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name);\n\nconst normalizeDataURL = (urlString, {stripHash}) => {\n\tconst match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);\n\n\tif (!match) {\n\t\tthrow new Error(`Invalid URL: ${urlString}`);\n\t}\n\n\tlet {type, data, hash} = match.groups;\n\tconst mediaType = type.split(';');\n\thash = stripHash ? '' : hash;\n\n\tlet isBase64 = false;\n\tif (mediaType[mediaType.length - 1] === 'base64') {\n\t\tmediaType.pop();\n\t\tisBase64 = true;\n\t}\n\n\t// Lowercase MIME type\n\tconst mimeType = (mediaType.shift() || '').toLowerCase();\n\tconst attributes = mediaType\n\t\t.map(attribute => {\n\t\t\tlet [key, value = ''] = attribute.split('=').map(string => string.trim());\n\n\t\t\t// Lowercase `charset`\n\t\t\tif (key === 'charset') {\n\t\t\t\tvalue = value.toLowerCase();\n\n\t\t\t\tif (value === DATA_URL_DEFAULT_CHARSET) {\n\t\t\t\t\treturn '';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn `${key}${value ? `=${value}` : ''}`;\n\t\t})\n\t\t.filter(Boolean);\n\n\tconst normalizedMediaType = [\n\t\t...attributes,\n\t];\n\n\tif (isBase64) {\n\t\tnormalizedMediaType.push('base64');\n\t}\n\n\tif (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {\n\t\tnormalizedMediaType.unshift(mimeType);\n\t}\n\n\treturn `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ''}`;\n};\n\nexport default function normalizeUrl(urlString, options) {\n\toptions = {\n\t\tdefaultProtocol: 'http:',\n\t\tnormalizeProtocol: true,\n\t\tforceHttp: false,\n\t\tforceHttps: false,\n\t\tstripAuthentication: true,\n\t\tstripHash: false,\n\t\tstripTextFragment: true,\n\t\tstripWWW: true,\n\t\tremoveQueryParameters: [/^utm_\\w+/i],\n\t\tremoveTrailingSlash: true,\n\t\tremoveSingleSlash: true,\n\t\tremoveDirectoryIndex: false,\n\t\tsortQueryParameters: true,\n\t\t...options,\n\t};\n\n\turlString = urlString.trim();\n\n\t// Data URL\n\tif (/^data:/i.test(urlString)) {\n\t\treturn normalizeDataURL(urlString, options);\n\t}\n\n\tif (/^view-source:/i.test(urlString)) {\n\t\tthrow new Error('`view-source:` is not supported as it is a non-standard protocol');\n\t}\n\n\tconst hasRelativeProtocol = urlString.startsWith('//');\n\tconst isRelativeUrl = !hasRelativeProtocol && /^\\.*\\//.test(urlString);\n\n\t// Prepend protocol\n\tif (!isRelativeUrl) {\n\t\turlString = urlString.replace(/^(?!(?:\\w+:)?\\/\\/)|^\\/\\//, options.defaultProtocol);\n\t}\n\n\tconst urlObject = new URL(urlString);\n\n\tif (options.forceHttp && options.forceHttps) {\n\t\tthrow new Error('The `forceHttp` and `forceHttps` options cannot be used together');\n\t}\n\n\tif (options.forceHttp && urlObject.protocol === 'https:') {\n\t\turlObject.protocol = 'http:';\n\t}\n\n\tif (options.forceHttps && urlObject.protocol === 'http:') {\n\t\turlObject.protocol = 'https:';\n\t}\n\n\t// Remove auth\n\tif (options.stripAuthentication) {\n\t\turlObject.username = '';\n\t\turlObject.password = '';\n\t}\n\n\t// Remove hash\n\tif (options.stripHash) {\n\t\turlObject.hash = '';\n\t} else if (options.stripTextFragment) {\n\t\turlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, '');\n\t}\n\n\t// Remove duplicate slashes if not preceded by a protocol\n\t// NOTE: This could be implemented using a single negative lookbehind\n\t// regex, but we avoid that to maintain compatibility with older js engines\n\t// which do not have support for that feature.\n\tif (urlObject.pathname) {\n\t\t// TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\\b[a-z][a-z\\d+\\-.]{1,50}:)\\/{2,}/g, '/');` when Safari supports negative lookbehind.\n\n\t\t// Split the string by occurrences of this protocol regex, and perform\n\t\t// duplicate-slash replacement on the strings between those occurrences\n\t\t// (if any).\n\t\tconst protocolRegex = /\\b[a-z][a-z\\d+\\-.]{1,50}:\\/\\//g;\n\n\t\tlet lastIndex = 0;\n\t\tlet result = '';\n\t\tfor (;;) {\n\t\t\tconst match = protocolRegex.exec(urlObject.pathname);\n\t\t\tif (!match) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst protocol = match[0];\n\t\t\tconst protocolAtIndex = match.index;\n\t\t\tconst intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);\n\n\t\t\tresult += intermediate.replace(/\\/{2,}/g, '/');\n\t\t\tresult += protocol;\n\t\t\tlastIndex = protocolAtIndex + protocol.length;\n\t\t}\n\n\t\tconst remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);\n\t\tresult += remnant.replace(/\\/{2,}/g, '/');\n\n\t\turlObject.pathname = result;\n\t}\n\n\t// Decode URI octets\n\tif (urlObject.pathname) {\n\t\ttry {\n\t\t\turlObject.pathname = decodeURI(urlObject.pathname);\n\t\t} catch {}\n\t}\n\n\t// Remove directory index\n\tif (options.removeDirectoryIndex === true) {\n\t\toptions.removeDirectoryIndex = [/^index\\.[a-z]+$/];\n\t}\n\n\tif (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {\n\t\tlet pathComponents = urlObject.pathname.split('/');\n\t\tconst lastComponent = pathComponents[pathComponents.length - 1];\n\n\t\tif (testParameter(lastComponent, options.removeDirectoryIndex)) {\n\t\t\tpathComponents = pathComponents.slice(0, -1);\n\t\t\turlObject.pathname = pathComponents.slice(1).join('/') + '/';\n\t\t}\n\t}\n\n\tif (urlObject.hostname) {\n\t\t// Remove trailing dot\n\t\turlObject.hostname = urlObject.hostname.replace(/\\.$/, '');\n\n\t\t// Remove `www.`\n\t\tif (options.stripWWW && /^www\\.(?!www\\.)[a-z\\-\\d]{1,63}\\.[a-z.\\-\\d]{2,63}$/.test(urlObject.hostname)) {\n\t\t\t// Each label should be max 63 at length (min: 1).\n\t\t\t// Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names\n\t\t\t// Each TLD should be up to 63 characters long (min: 2).\n\t\t\t// It is technically possible to have a single character TLD, but none currently exist.\n\t\t\turlObject.hostname = urlObject.hostname.replace(/^www\\./, '');\n\t\t}\n\t}\n\n\t// Remove query unwanted parameters\n\tif (Array.isArray(options.removeQueryParameters)) {\n\t\tfor (const key of [...urlObject.searchParams.keys()]) {\n\t\t\tif (testParameter(key, options.removeQueryParameters)) {\n\t\t\t\turlObject.searchParams.delete(key);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (options.removeQueryParameters === true) {\n\t\turlObject.search = '';\n\t}\n\n\t// Sort query parameters\n\tif (options.sortQueryParameters) {\n\t\turlObject.searchParams.sort();\n\t}\n\n\tif (options.removeTrailingSlash) {\n\t\turlObject.pathname = urlObject.pathname.replace(/\\/$/, '');\n\t}\n\n\tconst oldUrlString = urlString;\n\n\t// Take advantage of many of the Node `url` normalizations\n\turlString = urlObject.toString();\n\n\tif (!options.removeSingleSlash && urlObject.pathname === '/' && !oldUrlString.endsWith('/') && urlObject.hash === '') {\n\t\turlString = urlString.replace(/\\/$/, '');\n\t}\n\n\t// Remove ending `/` unless removeSingleSlash is false\n\tif ((options.removeTrailingSlash || urlObject.pathname === '/') && urlObject.hash === '' && options.removeSingleSlash) {\n\t\turlString = urlString.replace(/\\/$/, '');\n\t}\n\n\t// Restore relative protocol, if applicable\n\tif (hasRelativeProtocol && !options.normalizeProtocol) {\n\t\turlString = urlString.replace(/^http:\\/\\//, '//');\n\t}\n\n\t// Remove http/https\n\tif (options.stripProtocol) {\n\t\turlString = urlString.replace(/^(?:https?:)?\\/\\//, '');\n\t}\n\n\treturn urlString;\n}\n","/*! get-canonical-url | MIT | https://github.com/jakejarvis/get-canonical-url */\nimport normalizeUrl from \"normalize-url\";\nimport type { Options as NormalizeOptions } from \"normalize-url\";\n\nexport interface Options {\n /**\n * Clean-up and normalize the determined canonical URL.\n *\n * @default false\n */\n readonly normalize?: boolean;\n\n /**\n * Options passed directly to [`normalize-url`](https://github.com/sindresorhus/normalize-url#options).\n *\n * Requires `options.normalize = true`.\n *\n * @default { stripWWW: false, stripHash: true, removeQueryParameters: true, removeTrailingSlash: false }\n */\n readonly normalizeOptions?: NormalizeOptions;\n\n /**\n * Make an educated guess using other clues if canonical isn't explicitly set in the page's `<head>`.\n *\n * @default false\n */\n readonly guess?: boolean;\n}\n\n/**\n * Returns the current page's canonical URL.\n *\n * @example\n * ```\n * // This imaginary page's <head> contains the following link tag:\n * // <link rel=\"canonical\" href=\"https://www.example.com/\" />\n *\n * import canonicalUrl from \"get-canonical-url\";\n *\n * canonicalUrl();\n * //=> 'https://www.example.com/'\n * ```\n */\nexport default function canonicalUrl(options: Options = {}): string | undefined {\n options = {\n normalize: false,\n normalizeOptions: {\n // A few sensible normalize-url defaults:\n // https://github.com/sindresorhus/normalize-url#options\n stripWWW: false,\n stripHash: true,\n removeQueryParameters: true,\n removeTrailingSlash: false,\n },\n guess: false,\n ...options,\n };\n\n // Start with a blank slate\n let url: string | undefined = undefined;\n\n // Look for a <link rel=\"canonical\"> tag in the page's <head>\n const linkElement: HTMLLinkElement | null = document.head.querySelector(\"link[rel='canonical']\");\n\n if (linkElement !== null) {\n // Easy peasy, there was a <link rel=\"canonical\"> tag!\n url = linkElement.href;\n } else if (options.guess) {\n // We've been told to make an educated guess if canonical isn't explicitly set\n url = document.documentURI || document.URL || window.location.href;\n }\n\n if (url && options.normalize) {\n // Pass either custom options or defaults (above) directly to normalize-url\n url = normalizeUrl(url, options.normalizeOptions);\n }\n\n // Some sort of URL has been determined by this point, unless it's impossible\n return url;\n}\n"],"names":["testParameter","name","filters","some","filter","RegExp","test","options","normalize","normalizeOptions","stripWWW","stripHash","removeQueryParameters","removeTrailingSlash","guess","url","undefined","linkElement","document","head","querySelector","href","documentURI","URL","window","location","urlString","defaultProtocol","normalizeProtocol","forceHttp","forceHttps","stripAuthentication","stripTextFragment","removeSingleSlash","removeDirectoryIndex","sortQueryParameters","trim","match","exec","Error","type","data","hash","groups","mediaType","split","isBase64","length","pop","mimeType","shift","toLowerCase","normalizedMediaType","map","attribute","key","value","string","Boolean","push","unshift","join","normalizeDataURL","hasRelativeProtocol","startsWith","replace","urlObject","protocol","username","password","pathname","protocolRegex","lastIndex","result","protocolAtIndex","index","slice","decodeURI","Array","isArray","pathComponents","hostname","searchParams","keys","delete","search","sort","oldUrlString","toString","endsWith","stripProtocol","normalizeUrl"],"mappings":"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"}