Skip to content

Esm 缺失扩展名#9466

Open
chenshuai2144 wants to merge 3 commits intomasterfrom
cursor/esm-df3e
Open

Esm 缺失扩展名#9466
chenshuai2144 wants to merge 3 commits intomasterfrom
cursor/esm-df3e

Conversation

@chenshuai2144
Copy link
Contributor

@chenshuai2144 chenshuai2144 commented Feb 10, 2026

为 ESM 产物中的相对导入添加 .js 扩展名,以兼容严格 ESM 构建工具。

此 PR 修复了在使用 Rspack、Vite 或 Webpack 5 + "type": "module" 等严格 ESM 兼容的构建工具时,由于 @ant-design/pro-components/es/index.js 中导出语句缺少 .js 扩展名导致的构建失败。解决方案是保留 father 构建,并添加一个构建后脚本 scripts/add-esm-extensions.mjs 来自动补全 .js 扩展名。


Open in Cursor Open in Web

Summary by CodeRabbit

版本发布说明

  • Chores
    • 优化构建工具链,采用新的模块打包系统改进 ESM 和 CJS 格式的生成流程。
    • 增强构建脚本以优化输出文件的组织结构。
    • 更新项目构建配置和依赖项。

- Add scripts/add-esm-extensions.mjs to append .js extension to relative imports in es/ output
- Update build script to run add-esm-extensions after father build
- Fixes build failures with strict ESM tools (Rspack, Vite, Webpack 5 + type: module)
- ESM spec requires fully specified paths (e.g. ./card/index.js not ./card)

Co-authored-by: 陈帅 <wasd2144@hotmail.com>
@cursor
Copy link

cursor bot commented Feb 10, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • tsc_output.txt

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

漫游

该变更将项目的构建工具链从 Father 迁移为 tsup + Father 的混合方案。tsup 现在负责生成 ESM 和 CJS 产物,而 Father 仅负责 UMD 构建。新增脚本用于重新组织输出结构和规范化 ESM 导入路径。

变更

Cohort / File(s) Summary
构建配置迁移
.fatherrc.ts, tsup.config.ts
从 Father 配置中移除 esm 和 cjs 块,改由 tsup 处理。新增 tsup 配置,指定 ESM 和 CJS 输出到 dist-tsup,启用类型声明和源映射。
忽略文件与依赖
.gitignore, package.json
添加 dist-tsup 到忽略列表。更新构建脚本为 tsup && node scripts/tsup-restructure.mjs && father build,添加 tsup 依赖(版本 ^8.5.1)。
构建输出处理脚本
scripts/tsup-restructure.mjs, scripts/add-esm-extensions.mjs
新增 tsup-restructure.mjs,将 dist-tsup 的产物重新组织到 es/ 和 lib/ 目录。新增 add-esm-extensions.mjs,为 ESM 导入添加 .js 扩展名以兼容打包工具。

预计代码审查工作量

🎯 3 (中等) | ⏱️ ~25 分钟

诗歌

🐰 兔兔的构建之歌

tsup 和 Father 齐上阵,
ESM、CJS 各就各的岗,
脚本重组巧安排,
扩展名补齐妙如仙,
构建流程焕新颜!🐾✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Esm 缺失扩展名" (Missing ESM extensions) accurately reflects the main objective of the PR, which is to add .js extensions to relative imports in ESM output to support strict ESM build tools.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/esm-df3e

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @chenshuai2144, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在增强项目对现代 ESM 构建工具的兼容性。通过引入一个自动化的构建后脚本,它确保所有 ESM 产物中的相对导入和导出路径都包含必要的 .js 扩展名。这一改进解决了在严格 ESM 环境下(如 Rspack、Vite 或 Webpack 5 配合 "type": "module")可能出现的构建错误,从而提升了项目的可移植性和构建稳定性。

Highlights

  • ESM 兼容性修复: 为 ESM 产物中的相对导入/导出路径添加 .js 扩展名,以解决在使用 Rspack、Vite 或 Webpack 5 等严格 ESM 兼容的构建工具时,因缺少扩展名导致的构建失败问题。
  • 新增构建后脚本: 引入了一个新的 Node.js 脚本 scripts/add-esm-extensions.mjs,该脚本会在 father build 命令执行后自动运行,负责遍历 ESM 输出目录并修正导入路径。
  • 构建流程更新: 修改了 package.json 中的 build 脚本,使其在完成 father build 后,额外执行 scripts/add-esm-extensions.mjs 脚本。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • package.json
    • 更新了 build 脚本,在 father build 之后添加了执行 scripts/add-esm-extensions.mjs 的步骤。
  • scripts/add-esm-extensions.mjs
    • 新增了一个 Node.js 脚本,用于遍历 es 目录下的所有 .js 文件,并为其中的相对导入/导出路径自动补全 .js 扩展名。
Activity
  • 没有提供关于此拉取请求的任何活动信息。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@socket-security
Copy link

socket-security bot commented Feb 10, 2026

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 通过添加一个构建后脚本来为 ESM 产物中的相对导入补充 .js 扩展名,解决了在严格 ESM 构建工具中的兼容性问题。这个方法是有效的。我对新添加的 scripts/add-esm-extensions.mjs 脚本提出了一些改进建议,主要涉及更准确地解析文件路径、支持动态导入以及处理更多文件类型,以增强脚本的健壮性和准确性。

Comment on lines +18 to +30
function resolveFullySpecifiedPath(baseDir, specifier) {
if (EXTENSION_PATTERN.test(specifier)) return specifier;

const resolvedPath = path.resolve(baseDir, specifier);

if (fs.existsSync(resolvedPath + '.js')) return specifier + '.js';

if (fs.existsSync(path.join(resolvedPath, 'index.js'))) {
return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
}

return specifier + '.js';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

resolveFullySpecifiedPath 函数在处理无扩展名的导入路径时,可能不够健壮。当前实现会优先检查 .js 文件和 index.js,但对于其他类型的文件(如 .json),即使存在,它最终也会错误地回退到添加 .js 扩展名。例如,import data from './config'config.json 存在的情况下会被错误地转换为 import data from './config.js'

为了更准确地解析路径,建议在回退到默认添加 .js 之前,检查 EXTENSION_PATTERN 中定义的所有可能的文件扩展名(如 .js, .mjs, .cjs, .json)。

function resolveFullySpecifiedPath(baseDir, specifier) {
  if (EXTENSION_PATTERN.test(specifier)) return specifier;

  const resolvedPath = path.resolve(baseDir, specifier);

  for (const ext of ['.js', '.mjs', '.cjs', '.json']) {
    if (fs.existsSync(resolvedPath + ext)) {
      return specifier + ext;
    }
  }

  if (fs.existsSync(path.join(resolvedPath, 'index.js'))) {
    return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
  }

  return specifier + '.js';
}

function processFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const baseDir = path.dirname(filePath);
const relativePathRegex = /(?:from|import)\s+['"](\.\.[\/\\][^'"]*|\.\/[^'"]*)['"]/g;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

当前的正则表达式 relativePathRegex 无法匹配动态导入 import('...') 的语法。如果代码库中存在动态导入,它们的路径将不会被添加 .js 扩展名,这可能会导致在严格的 ESM 环境中出现运行时错误。

建议修改正则表达式以兼容动态导入语法。

  const relativePathRegex = /(?:from|import)\s*\(?\s*['"](\.\.[\/\\][^'"]*|\.\/[^'"]*)['"]/g;

for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) walkDir(fullPath, callback);
else if (entry.name.endsWith('.js')) callback(fullPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

walkDir 函数目前只处理以 .js 结尾的文件。如果 es 目录中包含其他类型的 ESM 文件(例如 .mjs),这些文件将被忽略,导致它们的导入路径不会被修正。

为了使脚本更通用,建议修改文件检查逻辑,以包含所有相关的 JavaScript 模块文件类型。

    else if (/\.m?js$/.test(entry.name)) callback(fullPath);

@chenshuai2144 chenshuai2144 marked this pull request as ready for review February 11, 2026 03:26
Copilot AI review requested due to automatic review settings February 11, 2026 03:26
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Feb 11, 2026
- Add tsup for ESM and CJS build (es/, lib/)
- Configure father to only generate UMD (dist/)
- Add scripts/tsup-restructure.mjs for output reorganization
- Add tsup.config.ts with legacyOutput for es/lib structure

Co-authored-by: 陈帅 <wasd2144@hotmail.com>
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Feb 11, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 通过在构建后对 es/ 产物进行一次“补全相对导入/导出 .js 扩展名”的处理,解决严格 ESM 工具链(Rspack / Vite / Webpack5 + "type":"module" 等)下因未 fully-specified 导致的构建失败问题。

Changes:

  • 新增构建后脚本 scripts/add-esm-extensions.mjs,遍历 es/ 下的 .js 文件并补全相对导入/导出路径的 .js 扩展名
  • 调整 package.jsonbuild 脚本,在 father build 后执行该补全脚本

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 4 comments.

File Description
scripts/add-esm-extensions.mjs 新增构建后处理脚本,尝试将 es/ 产物中的相对导入/导出路径改为 fully-specified(补 .js
package.json 将补全脚本串联进 build 流程,确保发布前产物被修正

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +19
const EXTENSION_PATTERN = /\.(js|mjs|cjs|json)$/;

function resolveFullySpecifiedPath(baseDir, specifier) {
if (EXTENSION_PATTERN.test(specifier)) return specifier;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXTENSION_PATTERN only treats .js/.mjs/.cjs/.json as “already fully specified”. If a relative specifier already has some other extension (e.g. .css/.svg/.wasm), this script will incorrectly append .js and rewrite it to something like ./asset.svg.js. Consider treating any path with a file extension as already fully specified (e.g. via path.extname() / path.posix.extname()), instead of a hard-coded allowlist.

Suggested change
const EXTENSION_PATTERN = /\.(js|mjs|cjs|json)$/;
function resolveFullySpecifiedPath(baseDir, specifier) {
if (EXTENSION_PATTERN.test(specifier)) return specifier;
function resolveFullySpecifiedPath(baseDir, specifier) {
const hasExtension = path.posix.extname(specifier) !== '';
if (hasExtension) return specifier;

Copilot uses AI. Check for mistakes.
return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
}

return specifier + '.js';
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveFullySpecifiedPath() falls back to returning specifier + '.js' even when neither ${resolvedPath}.js nor ${resolvedPath}/index.js exists. That can silently produce invalid import paths in the output. Consider only rewriting when a target file is found (otherwise leave unchanged or emit a warning/error).

Suggested change
return specifier + '.js';
return specifier;

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +48
newContent = newContent.replace(relativePathRegex, (match) => {
const pathMatch = match.match(/['"]([^'"]+)['"]/);
if (!pathMatch || !pathMatch[1].startsWith('.')) return match;

const normalized = pathMatch[1].replace(/\\/g, '/');
const resolved = resolveFullySpecifiedPath(baseDir, normalized);
if (resolved !== normalized) {
hasChanges = true;
return match.replace(pathMatch[1], resolved);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replace callback re-parses the matched substring with match.match(...) to extract the specifier. Since relativePathRegex already captures the specifier, using the callback’s capture-group argument avoids the extra regex pass and clarifies intent.

Suggested change
newContent = newContent.replace(relativePathRegex, (match) => {
const pathMatch = match.match(/['"]([^'"]+)['"]/);
if (!pathMatch || !pathMatch[1].startsWith('.')) return match;
const normalized = pathMatch[1].replace(/\\/g, '/');
const resolved = resolveFullySpecifiedPath(baseDir, normalized);
if (resolved !== normalized) {
hasChanges = true;
return match.replace(pathMatch[1], resolved);
newContent = newContent.replace(relativePathRegex, (match, specifier) => {
if (!specifier || !specifier.startsWith('.')) return match;
const normalized = specifier.replace(/\\/g, '/');
const resolved = resolveFullySpecifiedPath(baseDir, normalized);
if (resolved !== normalized) {
hasChanges = true;
return match.replace(specifier, resolved);

Copilot uses AI. Check for mistakes.
Comment on lines 31 to 33
"bootstrap": "node ./scripts/bootstrap.mjs",
"build": "father build",
"build": "tsup && node scripts/tsup-restructure.mjs && father build",
"changelog": "node ./scripts/changelogs.mjs",
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Script paths elsewhere in this file use node ./scripts/<name>.mjs, but build uses node scripts/add-esm-extensions.mjs (missing ./). Aligning this keeps script invocation consistent and avoids surprises in tooling that assumes ./scripts/... patterns.

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@package.json`:
- Line 32: The build script in package.json misses invoking
scripts/add-esm-extensions.mjs so ESM outputs won't get .js extensions; update
the "build" pipeline to run the add-esm-extensions script after
tsup-restructure.mjs (and before or after father build) so the ESM files are
post-processed—specifically ensure the current "tsup && node
scripts/tsup-restructure.mjs && father build" sequence includes a step to call
node scripts/add-esm-extensions.mjs (referencing scripts/add-esm-extensions.mjs,
tsup-restructure.mjs, and the build script entry) so ESM relative imports
receive .js extensions.

In `@scripts/add-esm-extensions.mjs`:
- Around line 18-30: The fallback in resolveFullySpecifiedPath blindly appends
".js" even when neither specifier.js nor specifier/index.js exist, which can
produce invalid MODULE_NOT_FOUND paths; update resolveFullySpecifiedPath to,
after checking EXTENSION_PATTERN, .js file and index.js, avoid forcibly
returning specifier + '.js' — instead return the original specifier (or keep it
unchanged) and emit a warning via console.warn or a logger that the specifier
could not be resolved, referencing the resolveFullySpecifiedPath function and
the specifier/resolvedPath variables when constructing the message.

In `@tsup.config.ts`:
- Around line 1-18: ESLint fails because tsup.config.ts (the file exporting
defineConfig from 'tsup') isn't included in the TypeScript project used by the
linter; update your TypeScript config by adding "tsup.config.ts" to the
tsconfig.json "include" array so the TypeScript parser can resolve this file, or
alternatively add an ESLint ignore rule (e.g., add "tsup.config.ts" to eslint's
ignorePatterns) so the linter skips it; modify either the tsconfig.json include
list or the ESLint ignorePatterns accordingly and rerun CI.
🧹 Nitpick comments (5)
scripts/tsup-restructure.mjs (2)

34-44: CJS 回退逻辑可能将非预期文件移入 lib/

dist-tsup/cjs 目录不存在时,回退逻辑会将 dist-tsup 根目录下除 esm/cjs 以外的所有文件移入 lib/,这可能包括 .d.ts.d.ts.map、sourcemap 等不应出现在 CJS 产出中的文件。建议增加文件过滤条件,只移入 .js.js.map 文件。

♻️ 建议增加文件后缀过滤
     for (const name of fs.readdirSync(root)) {
-      if (name !== 'esm' && name !== 'cjs') {
+      if (name !== 'esm' && name !== 'cjs' && (name.endsWith('.js') || name.endsWith('.js.map'))) {
         fs.renameSync(path.join(root, name), path.join('lib', name));
       }
     }

19-21: rmSyncrenameSync 缺乏错误处理。

如果 renameSync 失败(例如跨文件系统),脚本会直接崩溃,且 es/ 目录已被删除,导致构建产物丢失。对于构建关键路径上的脚本,建议增加 try-catch 或使用 cpSync + rmSync 作为 renameSync 的降级方案。

scripts/add-esm-extensions.mjs (2)

35-35: 正则表达式未覆盖动态 import() 语法。

当前正则 (?:from|import)\s+['"]... 可以匹配静态 import 声明和 from 子句(包括 export ... from),但无法匹配 import('./foo') 形式的动态导入。鉴于 tsup 配置中 splitting: false,动态导入在当前产物中可能不存在,但如果未来启用 code splitting 或有其他使用场景,建议关注此限制。


40-48: 替换回调中重复进行了路径提取。

正则 relativePathRegex 已经捕获了组 1(相对路径),但 replace 回调未使用捕获组,而是在第 41 行用第二个正则重新提取路径。可利用 replace 回调的参数直接获取捕获组,减少冗余解析:

♻️ 简化回调
-  newContent = newContent.replace(relativePathRegex, (match) => {
-    const pathMatch = match.match(/['"]([^'"]+)['"]/);
-    if (!pathMatch || !pathMatch[1].startsWith('.')) return match;
-
-    const normalized = pathMatch[1].replace(/\\/g, '/');
+  newContent = newContent.replace(relativePathRegex, (match, captured) => {
+    if (!captured || !captured.startsWith('.')) return match;
+
+    const normalized = captured.replace(/\\/g, '/');
     const resolved = resolveFullySpecifiedPath(baseDir, normalized);
     if (resolved !== normalized) {
       hasChanges = true;
-      return match.replace(pathMatch[1], resolved);
+      return match.replace(captured, resolved);
     }
     return match;
   });
tsup.config.ts (1)

16-16: legacyOutput 在 tsup 中仍然有效,不存在弃用。

legacyOutput 仍然是 tsup 的有效配置选项,并未被弃用。如果目标是将不同格式的输出分别放在不同的目录中,legacyOutput 仍可继续使用。

不过,可以考虑的替代方案包括:

  • 使用 outExtension 在单一目录中使用不同的文件扩展名(如 .mjs.cjs
  • 使用多配置模式,为不同格式分别指定 outDir

"scripts": {
"bootstrap": "node ./scripts/bootstrap.mjs",
"build": "father build",
"build": "tsup && node scripts/tsup-restructure.mjs && father build",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

add-esm-extensions.mjs 未在构建脚本中调用——PR 的核心功能缺失。

当前构建脚本为 "tsup && node scripts/tsup-restructure.mjs && father build",但 scripts/add-esm-extensions.mjs(为 ESM 相对导入添加 .js 扩展名的脚本)从未被调用。这是本 PR 要解决的核心问题,缺少这一步意味着 ESM 产物中依旧缺少 .js 扩展名。

应在 tsup-restructure.mjs 之后、father build 之前(或之后)添加对该脚本的调用:

🐛 修复建议
-    "build": "tsup && node scripts/tsup-restructure.mjs && father build",
+    "build": "tsup && node scripts/tsup-restructure.mjs && node scripts/add-esm-extensions.mjs && father build",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"build": "tsup && node scripts/tsup-restructure.mjs && father build",
"build": "tsup && node scripts/tsup-restructure.mjs && node scripts/add-esm-extensions.mjs && father build",
🤖 Prompt for AI Agents
In `@package.json` at line 32, The build script in package.json misses invoking
scripts/add-esm-extensions.mjs so ESM outputs won't get .js extensions; update
the "build" pipeline to run the add-esm-extensions script after
tsup-restructure.mjs (and before or after father build) so the ESM files are
post-processed—specifically ensure the current "tsup && node
scripts/tsup-restructure.mjs && father build" sequence includes a step to call
node scripts/add-esm-extensions.mjs (referencing scripts/add-esm-extensions.mjs,
tsup-restructure.mjs, and the build script entry) so ESM relative imports
receive .js extensions.

Comment on lines +18 to +30
function resolveFullySpecifiedPath(baseDir, specifier) {
if (EXTENSION_PATTERN.test(specifier)) return specifier;

const resolvedPath = path.resolve(baseDir, specifier);

if (fs.existsSync(resolvedPath + '.js')) return specifier + '.js';

if (fs.existsSync(path.join(resolvedPath, 'index.js'))) {
return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
}

return specifier + '.js';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

当目标文件不存在时,回退逻辑仍会盲目追加 .js,可能产生无效路径。

第 29 行的最终 fallback 在 specifier.jsspecifier/index.js 均不存在时,仍然返回 specifier + '.js'。这可能将一个本已正确的裸路径(如指向目录且 tsup 产出结构不同于预期时)改为一个不存在的 .js 路径,导致运行时 MODULE_NOT_FOUND

建议在 fallback 时保留原始 specifier 不变,或至少输出一条警告:

♻️ 建议:无法解析时保留原路径并打印警告
   if (fs.existsSync(path.join(resolvedPath, 'index.js'))) {
     return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
   }

-  return specifier + '.js';
+  // 无法确认目标文件存在,保留原始路径并警告
+  console.warn(`[warn] Cannot resolve: ${specifier} from ${baseDir}, keeping as-is`);
+  return specifier;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function resolveFullySpecifiedPath(baseDir, specifier) {
if (EXTENSION_PATTERN.test(specifier)) return specifier;
const resolvedPath = path.resolve(baseDir, specifier);
if (fs.existsSync(resolvedPath + '.js')) return specifier + '.js';
if (fs.existsSync(path.join(resolvedPath, 'index.js'))) {
return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
}
return specifier + '.js';
}
function resolveFullySpecifiedPath(baseDir, specifier) {
if (EXTENSION_PATTERN.test(specifier)) return specifier;
const resolvedPath = path.resolve(baseDir, specifier);
if (fs.existsSync(resolvedPath + '.js')) return specifier + '.js';
if (fs.existsSync(path.join(resolvedPath, 'index.js'))) {
return (specifier.endsWith('/') ? specifier : specifier + '/') + 'index.js';
}
// 无法确认目标文件存在,保留原始路径并警告
console.warn(`[warn] Cannot resolve: ${specifier} from ${baseDir}, keeping as-is`);
return specifier;
}
🤖 Prompt for AI Agents
In `@scripts/add-esm-extensions.mjs` around lines 18 - 30, The fallback in
resolveFullySpecifiedPath blindly appends ".js" even when neither specifier.js
nor specifier/index.js exist, which can produce invalid MODULE_NOT_FOUND paths;
update resolveFullySpecifiedPath to, after checking EXTENSION_PATTERN, .js file
and index.js, avoid forcibly returning specifier + '.js' — instead return the
original specifier (or keep it unchanged) and emit a warning via console.warn or
a logger that the specifier could not be resolved, referencing the
resolveFullySpecifiedPath function and the specifier/resolvedPath variables when
constructing the message.

Comment on lines +1 to +18
import { defineConfig } from 'tsup';

/**
* tsup 构建 ESM 和 CJS,输出到 es/、lib/
* UMD 由 father 单独构建
*/
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
target: 'chrome109',
keepNames: true,
legacyOutput: true,
outDir: 'dist-tsup',
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current tsconfig include/exclude settings
echo "=== tsconfig.json ==="
cat tsconfig.json 2>/dev/null || echo "Not found"

echo ""
echo "=== ESLint configuration files ==="
for file in .eslintrc .eslintrc.js .eslintrc.json .eslintrc.cjs .eslintrc.yml .eslintrc.yaml; do
  if [ -f "$file" ]; then
    echo "--- $file ---"
    cat "$file"
    echo ""
  fi
done

echo ""
echo "=== .eslintignore ==="
cat .eslintignore 2>/dev/null || echo "No .eslintignore found"

echo ""
echo "=== tsup.config.ts location ==="
ls -la tsup.config.ts 2>/dev/null || echo "File not found in root"

echo ""
echo "=== package.json (for tsup version) ==="
cat package.json | head -50

Repository: ant-design/pro-components

Length of output: 13137


🏁 Script executed:

#!/bin/bash
# Check tsup version
echo "=== Checking tsup version in package.json ==="
grep -A 5 -B 5 '"tsup"' package.json

echo ""
echo "=== package-lock.json or pnpm-lock.yaml for tsup version ==="
if [ -f "pnpm-lock.yaml" ]; then
  echo "Found pnpm-lock.yaml, checking tsup..."
  grep -A 5 "tsup:" pnpm-lock.yaml | head -20
elif [ -f "package-lock.json" ]; then
  echo "Found package-lock.json, checking tsup..."
  grep -A 5 '"tsup"' package-lock.json | head -20
fi

Repository: ant-design/pro-components

Length of output: 796


🌐 Web query:

tsup 8.5.1 legacyOutput deprecated option

💡 Result:

In tsup v8.5.1, legacyOutput is the flag that puts different formats into separate folders (e.g. dist/cjs/* and dist/esm/*) instead of using different file extensions. Its schema description is: “Output different formats to different folder instead of using different extension”. [1]

If you’re seeing a deprecation warning for legacyOutput, the supported path forward is to stop using the folder-splitting layout and use extension-based output, optionally customized via outExtension (available in the v8.5.1 config types). [2]

Example replacement:

// tsup.config.ts
import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/index.ts"],
  format: ["cjs", "esm"],
  // legacyOutput: true, // remove
  outExtension({ format }) {
    return { js: format === "cjs" ? ".cjs" : ".mjs" };
  },
});

Sources: [1] (tsup 8.5.1 schema.json), [2] (tsup 8.5.1 dist/index.d.ts).


CI 失败:ESLint 无法解析此文件,因为 tsup.config.ts 未包含在 TypeScript 配置中。

tsup.config.ts 不在 tsconfig.jsoninclude 列表中,但 ESLint 配置中指定了 project: './tsconfig.json',导致 TypeScript 解析器无法处理此文件。需要将 tsup.config.ts 加入 tsconfig.jsoninclude 列表,或在 ESLint 的 ignorePatterns 中添加此文件的忽略规则。

🧰 Tools
🪛 GitHub Actions: Build CI

[error] 1-1: ESLint parsing error: TS config does not include this file. tsup.config.ts is not included in the configured TS config. This caused 120 problems (1 error, 119 warnings).

🤖 Prompt for AI Agents
In `@tsup.config.ts` around lines 1 - 18, ESLint fails because tsup.config.ts (the
file exporting defineConfig from 'tsup') isn't included in the TypeScript
project used by the linter; update your TypeScript config by adding
"tsup.config.ts" to the tsconfig.json "include" array so the TypeScript parser
can resolve this file, or alternatively add an ESLint ignore rule (e.g., add
"tsup.config.ts" to eslint's ignorePatterns) so the linter skips it; modify
either the tsconfig.json include list or the ESLint ignorePatterns accordingly
and rerun CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants