feat: Configurable normalizeHref

未关闭
#4,584 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
35/100
Issue 类型
功能
描述清晰度
基本清楚
活跃度
停滞
技术栈
typescript
领域
build-system

调研方向

先定位 crawlAnchorsForNextUrls 和 standardNormalizeHref,然后检查 prerenderConfig 选项的定义和使用方式。确认可配置的 normalizeHref hook 应如何与现有的尾部斜杠行为交互,并添加覆盖测试,证明已配置的行为会被应用,同时默认行为保持不变。

由索引模型根据 Issue 内容生成。

描述

Feature: Want this? Upvote it!
Prerequisites
Describe the Feature Request

In order to prerender pages it would be great to configure the behaviour of normalizing the href.

Describe the Use Case

I have hrefs with and without trailing slash. The only thing which can be configured is using trailing slash or not. If setting trailing slash to true all href will have a trailing slash in the end, if set to false all href won`t have a trailing slash. I would suggest to add the possibility make this configurable. Either by adding a the possibility to do something like trailingSlash: 'source' in the config or by adding the possibility to overwrite the standardNormalizeHref function.

Describe Preferred Solution

option number 1

const standardNormalizeHref = (prerenderConfig, diagnostics, url) => {
  try {
    if (url != null && typeof url.href === 'string') {
      let href = url.href.trim();
      if (prerenderConfig.trailingSlash === true) {
        // url should have a trailing slash
        if (!href.endsWith('/')) {
          const parts = url.pathname.split('/');
          const lastPart = parts[parts.length - 1];
          if (!lastPart.includes('.')) {
            // does not end with a slash and last part does not have a dot
            href += '/';
          }
        }
      }
      else if (prerenderConfig.trailingSlash === false ){
        // url should NOT have a trailing slash
        if (href.endsWith('/') && url.pathname !== '/') {
          // this has a trailing slash and it's not the root path
          href = href.slice(0, -1);
        }
      }
      return href;
    }
  }
  catch (e) {
    catchError(diagnostics, e);
  }
  return null;
};

option number 2

const crawlAnchorsForNextUrls = (prerenderConfig, diagnostics, baseUrl, currentUrl, parsedAnchors) => {
  if (!Array.isArray(parsedAnchors) || parsedAnchors.length === 0) {
    return [];
  }
  const basePathParts = baseUrl.pathname.split('/');
  // filterAnchor(): filter which anchors to actually crawl
  // normalizeUrl(): normalize href strings into URL objects
  // filterUrl(): filter which urls to actually crawl
  // normalizeHref(): normalize URL objects into href strings
  return parsedAnchors
    .filter((anchor) => {
    // filter which anchors to actually crawl
    if (typeof prerenderConfig.filterAnchor === 'function') {
      // user filterAnchor()
      try {
        const userFilterAnchor = prerenderConfig.filterAnchor(anchor, currentUrl);
        if (userFilterAnchor === false) {
          return false;
        }
      }
      catch (e) {
        // user filterAnchor() error
        catchError(diagnostics, e);
        return false;
      }
    }
    // standard filterAnchor()
    return standardFilterAnchor(diagnostics, anchor);
  })
    .map((anchor) => {
    // normalize href strings into URL objects
    if (typeof prerenderConfig.normalizeUrl === 'function') {
      try {
        // user normalizeUrl()
        const userNormalizedUrl = prerenderConfig.normalizeUrl(anchor.href, currentUrl);
        // standard normalizeUrl(), after user normalized
        return standardNormalizeUrl(diagnostics, userNormalizedUrl.href, currentUrl);
      }
      catch (e) {
        // user normalizeUrl() error
        catchError(diagnostics, e);
      }
    }
    // standard normalizeUrl(), no user normalized
    return standardNormalizeUrl(diagnostics, anchor.href, currentUrl);
  })
    .filter((url) => {
    // filter which urls to actually crawl
    if (typeof prerenderConfig.filterUrl === 'function') {
      // user filterUrl()
      try {
        const userFilterUrl = prerenderConfig.filterUrl(url, currentUrl);
        if (userFilterUrl === false) {
          return false;
        }
      }
      catch (e) {
        // user filterUrl() error
        catchError(diagnostics, e);
        return false;
      }
    }
    // standard filterUrl()
    return standardFilterUrl(diagnostics, url, currentUrl, basePathParts);
  })
    .map((url) => {
    

if (typeof prerenderConfig.normalizeHref === 'function') {
      try {
        // user normalizeHref()
        return  prerenderConfig.normalizeHref(prerenderConfig, diagnostics, url);
      }
      catch (e) {
        // user normalizeUrl() error
        catchError(diagnostics, e);
      }
    }
// standard normalize href
    // normalize URL objects into href strings
    return standardNormalizeHref(prerenderConfig, diagnostics, url);
  })
    .reduce((hrefs, href) => {
    // remove any duplicate hrefs from the array
    if (!hrefs.includes(href)) {
      hrefs.push(href);
    }
    return hrefs;
  }, [])
    .sort((a, b) => {
    // sort the hrefs so the urls with the least amount
    // of directories are first, then by alphabetical
    const partsA = a.split('/').length;
    const partsB = b.split('/').length;
    if (partsA < partsB)
      return -1;
    if (partsA > partsB)
      return 1;
    if (a < b)
      return -1;
    if (a > b)
      return 1;
    return 0;
  });
};
Describe Alternatives

No response

Related Code

No response

Additional Information

No response

主要语言
TypeScript
星标
13.2k
派生
865
平均合并
2 天 16 小时
30 天内合并 PR
29

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

stenciljs/core 的其他 Issue

查看 stenciljs/core 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。