Skip to content

urlJoin

text
/**
 * A copy of https://github.com/moxystudio/js-proper-url-join/blob/master/src/index.js
 * but without the query handling.
 */

const urlRegExp = /^(\w+:\/\/[^/?]+)?(.*?)$/

export interface UrlJoinOptions {
  /**
   * Add a leading slash.
   *
   * **Default**: `true`
   */
  leadingSlash?: boolean | 'keep' | undefined
  /**
   * Add a trailing slash.
   *
   * **Default**: `false`
   */
  trailingSlash?: boolean | 'keep' | undefined
}

const normalizeParts = (parts: string[]) =>
  parts
    // Filter non-string or non-numeric values
    .filter((part) => typeof part === 'string' || typeof part === 'number')
    // Convert to strings
    .map((part) => `${part}`)
    // Remove empty parts
    .filter((part) => part)

const parseParts = (parts: string[]) => {
  const partsStr = parts.join('/')
  const [, prefix = '', pathname = ''] = partsStr.match(urlRegExp) || []

  return {
    prefix,
    pathname: {
      parts: pathname.split('/').filter((part) => part !== ''),
      hasLeading: /^\/+/.test(pathname),
      hasTrailing: /\/+$/.test(pathname)
    }
  }
}

const buildUrl = (parsedParts: ReturnType<typeof parseParts>, options: UrlJoinOptions) => {
  const { prefix, pathname } = parsedParts
  const { parts: pathnameParts, hasLeading, hasTrailing } = pathname
  const { leadingSlash, trailingSlash } = options

  const addLeading = leadingSlash === true || (leadingSlash === 'keep' && hasLeading)
  const addTrailing = trailingSlash === true || (trailingSlash === 'keep' && hasTrailing)

  // Start with prefix if not empty (http://google.com)
  let url = prefix

  // Add the parts
  if (pathnameParts.length > 0) {
    if (url || addLeading) {
      url += '/'
    }

    url += pathnameParts.join('/')
  }

  // Add trailing to the end
  if (addTrailing) {
    url += '/'
  }

  // Add leading if URL is still empty
  if (!url && addLeading) {
    url += '/'
  }

  return url
}

export const urlJoin = (...parts: Array<string | UrlJoinOptions>) => {
  const lastArg = parts[parts.length - 1]
  let options: UrlJoinOptions

  // If last argument is an object, then it's the options
  // Note that null is an object, so we verify if is truthy
  if (lastArg && typeof lastArg === 'object') {
    options = lastArg
    parts = parts.slice(0, -1)
  }

  // Parse options
  options = {
    leadingSlash: true,
    trailingSlash: false,
    ...options
  } as UrlJoinOptions

  // Normalize parts before parsing them
  parts = normalizeParts(parts as string[])

  // Split the parts into prefix, pathname
  // (scheme://host)(/pathnameParts.join('/'))
  const parsedParts = parseParts(parts as string[])

  // Finally build the url based on the parsedParts
  return buildUrl(parsedParts, options)
}

encodePath

ts
export function encodePath(path: string) {
  return encodeURIComponent(path).split('%2F').join('/');
}

Contributors

作者:Long Mo
字数统计:305 字
阅读时长:1 分钟
Long Mo
文章作者:Long Mo
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Longmo Docs