From 0c6a64e1c54741b0ff9805e8c1978a384166e1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:07:23 +0800 Subject: [PATCH 1/3] fix: make package exports loadable in Node --- package.json | 16 +++++++++++++++- scripts/test-node-exports.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 scripts/test-node-exports.js diff --git a/package.json b/package.json index 2cdd25634..b6c8d79b4 100644 --- a/package.json +++ b/package.json @@ -13,67 +13,80 @@ "exports": { ".": { "types": "./es/index.d.ts", + "node": "./lib/index.js", "import": "./es/index.js", "require": "./lib/index.js" }, "./assets/*": "./assets/*", "./generate": { "types": "./es/generate/index.d.ts", + "node": "./lib/generate/index.js", "import": "./es/generate/index.js", "require": "./lib/generate/index.js" }, "./generate/*": { "types": "./es/generate/*.d.ts", + "node": "./lib/generate/*.js", "import": "./es/generate/*.js", "require": "./lib/generate/*.js" }, "./interface": { "types": "./es/interface.d.ts", + "node": "./lib/interface.js", "import": "./es/interface.js", "require": "./lib/interface.js" }, "./locale/*": { "types": "./es/locale/*.d.ts", + "node": "./lib/locale/*.js", "import": "./es/locale/*.js", "require": "./lib/locale/*.js" }, "./lib/generate": { "types": "./es/generate/index.d.ts", + "node": "./lib/generate/index.js", "import": "./es/generate/index.js", "require": "./lib/generate/index.js" }, "./lib/generate/*": { "types": "./es/generate/*.d.ts", + "node": "./lib/generate/*.js", "import": "./es/generate/*.js", "require": "./lib/generate/*.js" }, "./lib/interface": { "types": "./es/interface.d.ts", + "node": "./lib/interface.js", "import": "./es/interface.js", "require": "./lib/interface.js" }, "./lib/locale/*": { "types": "./es/locale/*.d.ts", + "node": "./lib/locale/*.js", "import": "./es/locale/*.js", "require": "./lib/locale/*.js" }, "./es/generate": { "types": "./es/generate/index.d.ts", + "node": "./lib/generate/index.js", "import": "./es/generate/index.js", "require": "./lib/generate/index.js" }, "./es/generate/*": { "types": "./es/generate/*.d.ts", + "node": "./lib/generate/*.js", "import": "./es/generate/*.js", "require": "./lib/generate/*.js" }, "./es/interface": { "types": "./es/interface.d.ts", + "node": "./lib/interface.js", "import": "./es/interface.js", "require": "./lib/interface.js" }, "./es/locale/*": { "types": "./es/locale/*.d.ts", + "node": "./lib/locale/*.js", "import": "./es/locale/*.js", "require": "./lib/locale/*.js" } @@ -101,11 +114,12 @@ "compile": "father build && lessc assets/index.less assets/index.css", "browser-field": "node scripts/update-browser-field.js", "gh-pages": "cross-env GH_PAGES=1 npm run docs:build && npm run docs:deploy", - "prepublishOnly": "npm run compile && npm run browser-field && rc-np", + "prepublishOnly": "npm run compile && npm run test:node-exports && npm run browser-field && rc-np", "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md", "lint:tsc": "tsc -p tsconfig.json --noEmit", "prettier": "prettier --write --ignore-unknown .", "test": "rc-test", + "test:node-exports": "node scripts/test-node-exports.js", "coverage": "father test --coverage", "prepare": "husky", "tsc": "tsc --noEmit" diff --git a/scripts/test-node-exports.js b/scripts/test-node-exports.js new file mode 100644 index 000000000..7cc22fb1c --- /dev/null +++ b/scripts/test-node-exports.js @@ -0,0 +1,30 @@ +const assert = require('node:assert/strict'); + +const entryPoints = [ + '@rc-component/picker', + '@rc-component/picker/generate', + '@rc-component/picker/generate/moment', + '@rc-component/picker/interface', + '@rc-component/picker/locale/en_US', + '@rc-component/picker/lib/generate', + '@rc-component/picker/lib/generate/moment', + '@rc-component/picker/lib/interface', + '@rc-component/picker/lib/locale/en_US', + '@rc-component/picker/es/generate', + '@rc-component/picker/es/generate/moment', + '@rc-component/picker/es/interface', + '@rc-component/picker/es/locale/en_US', +]; + +async function testNodeExports() { + const commonJSModules = entryPoints.map((entryPoint) => require(entryPoint)); + const esModules = await Promise.all(entryPoints.map((entryPoint) => import(entryPoint))); + + assert.equal(commonJSModules.length, entryPoints.length); + assert.equal(esModules.length, entryPoints.length); +} + +testNodeExports().catch((error) => { + console.error(error); + process.exitCode = 1; +}); From 13028ec8925909e3ed86e3991ecf05e6b5da528c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:28:41 +0800 Subject: [PATCH 2/3] fix: preserve native ESM export shapes --- package.json | 15 +-------- scripts/rewrite-esm-imports.js | 57 ++++++++++++++++++++++++++++++++++ scripts/test-node-exports.js | 16 ++++++++++ 3 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 scripts/rewrite-esm-imports.js diff --git a/package.json b/package.json index b6c8d79b4..1559e8e6b 100644 --- a/package.json +++ b/package.json @@ -13,80 +13,67 @@ "exports": { ".": { "types": "./es/index.d.ts", - "node": "./lib/index.js", "import": "./es/index.js", "require": "./lib/index.js" }, "./assets/*": "./assets/*", "./generate": { "types": "./es/generate/index.d.ts", - "node": "./lib/generate/index.js", "import": "./es/generate/index.js", "require": "./lib/generate/index.js" }, "./generate/*": { "types": "./es/generate/*.d.ts", - "node": "./lib/generate/*.js", "import": "./es/generate/*.js", "require": "./lib/generate/*.js" }, "./interface": { "types": "./es/interface.d.ts", - "node": "./lib/interface.js", "import": "./es/interface.js", "require": "./lib/interface.js" }, "./locale/*": { "types": "./es/locale/*.d.ts", - "node": "./lib/locale/*.js", "import": "./es/locale/*.js", "require": "./lib/locale/*.js" }, "./lib/generate": { "types": "./es/generate/index.d.ts", - "node": "./lib/generate/index.js", "import": "./es/generate/index.js", "require": "./lib/generate/index.js" }, "./lib/generate/*": { "types": "./es/generate/*.d.ts", - "node": "./lib/generate/*.js", "import": "./es/generate/*.js", "require": "./lib/generate/*.js" }, "./lib/interface": { "types": "./es/interface.d.ts", - "node": "./lib/interface.js", "import": "./es/interface.js", "require": "./lib/interface.js" }, "./lib/locale/*": { "types": "./es/locale/*.d.ts", - "node": "./lib/locale/*.js", "import": "./es/locale/*.js", "require": "./lib/locale/*.js" }, "./es/generate": { "types": "./es/generate/index.d.ts", - "node": "./lib/generate/index.js", "import": "./es/generate/index.js", "require": "./lib/generate/index.js" }, "./es/generate/*": { "types": "./es/generate/*.d.ts", - "node": "./lib/generate/*.js", "import": "./es/generate/*.js", "require": "./lib/generate/*.js" }, "./es/interface": { "types": "./es/interface.d.ts", - "node": "./lib/interface.js", "import": "./es/interface.js", "require": "./lib/interface.js" }, "./es/locale/*": { "types": "./es/locale/*.d.ts", - "node": "./lib/locale/*.js", "import": "./es/locale/*.js", "require": "./lib/locale/*.js" } @@ -111,7 +98,7 @@ "build": "npm run docs:build", "docs:build": "dumi build", "docs:deploy": "gh-pages -d docs-dist", - "compile": "father build && lessc assets/index.less assets/index.css", + "compile": "father build && node scripts/rewrite-esm-imports.js && lessc assets/index.less assets/index.css", "browser-field": "node scripts/update-browser-field.js", "gh-pages": "cross-env GH_PAGES=1 npm run docs:build && npm run docs:deploy", "prepublishOnly": "npm run compile && npm run test:node-exports && npm run browser-field && rc-np", diff --git a/scripts/rewrite-esm-imports.js b/scripts/rewrite-esm-imports.js new file mode 100644 index 000000000..5a4ee4752 --- /dev/null +++ b/scripts/rewrite-esm-imports.js @@ -0,0 +1,57 @@ +const fs = require('node:fs'); +const path = require('node:path'); + +const esDirectory = path.resolve(__dirname, '../es'); +const moduleSpecifierPattern = /(\b(?:from|import)\s*(?:\(\s*)?)(['"])(\.\.?\/[^'"]+)\2(\s*\)?)/g; + +fs.writeFileSync(path.join(esDirectory, 'package.json'), '{\n "type": "module"\n}\n'); + +function collectJavaScriptFiles(directory) { + return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const entryPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + return collectJavaScriptFiles(entryPath); + } + + return entry.isFile() && entry.name.endsWith('.js') ? [entryPath] : []; + }); +} + +function resolveModuleSpecifier(filePath, specifier) { + if (path.extname(specifier)) { + return specifier; + } + + const absoluteSpecifier = path.resolve(path.dirname(filePath), specifier); + if (fs.existsSync(`${absoluteSpecifier}.js`)) { + return `${specifier}.js`; + } + if (fs.existsSync(path.join(absoluteSpecifier, 'index.js'))) { + return `${specifier}/index.js`; + } + + throw new Error(`Cannot resolve ${specifier} from ${path.relative(esDirectory, filePath)}`); +} + +let rewriteCount = 0; + +collectJavaScriptFiles(esDirectory).forEach((filePath) => { + const source = fs.readFileSync(filePath, 'utf8'); + const rewrittenSource = source.replace( + moduleSpecifierPattern, + (match, prefix, quote, specifier, suffix) => { + const rewrittenSpecifier = resolveModuleSpecifier(filePath, specifier); + if (rewrittenSpecifier !== specifier) { + rewriteCount += 1; + } + return `${prefix}${quote}${rewrittenSpecifier}${quote}${suffix}`; + }, + ); + + if (rewrittenSource !== source) { + fs.writeFileSync(filePath, rewrittenSource); + } +}); + +console.log(`Rewrote ${rewriteCount} ESM module specifiers.`); diff --git a/scripts/test-node-exports.js b/scripts/test-node-exports.js index 7cc22fb1c..9f7084e04 100644 --- a/scripts/test-node-exports.js +++ b/scripts/test-node-exports.js @@ -22,6 +22,22 @@ async function testNodeExports() { assert.equal(commonJSModules.length, entryPoints.length); assert.equal(esModules.length, entryPoints.length); + + const publicKeys = (module) => + Object.keys(module) + .filter((key) => key !== '__esModule' && key !== 'module.exports') + .sort(); + + commonJSModules.forEach((commonJSModule, index) => { + assert.deepEqual(publicKeys(esModules[index]), publicKeys(commonJSModule)); + }); + + assert.equal(esModules[0].default, esModules[0].Picker); + assert.equal(commonJSModules[0].default, commonJSModules[0].Picker); + assert.equal(typeof esModules[2].default.getNow, 'function'); + assert.equal(typeof commonJSModules[2].default.getNow, 'function'); + assert.equal(esModules[4].default.locale, 'en_US'); + assert.equal(commonJSModules[4].default.locale, 'en_US'); } testNodeExports().catch((error) => { From 4b1ccbf34493fc77c1a3dbcbca12d4e8d457b382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B3=E5=BB=B7=E5=AE=89?= <73953029+nrps9909@users.noreply.github.com> Date: Wed, 2 Sep 2026 00:37:29 +0800 Subject: [PATCH 3/3] fix: resolve NodeNext declaration imports --- package.json | 3 +- scripts/rewrite-esm-imports.js | 13 ++++---- scripts/test-node-types.js | 54 ++++++++++++++++++++++++++++++++++ src/PickerPanel/index.tsx | 2 +- 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 scripts/test-node-types.js diff --git a/package.json b/package.json index 1559e8e6b..4718e5328 100644 --- a/package.json +++ b/package.json @@ -101,12 +101,13 @@ "compile": "father build && node scripts/rewrite-esm-imports.js && lessc assets/index.less assets/index.css", "browser-field": "node scripts/update-browser-field.js", "gh-pages": "cross-env GH_PAGES=1 npm run docs:build && npm run docs:deploy", - "prepublishOnly": "npm run compile && npm run test:node-exports && npm run browser-field && rc-np", + "prepublishOnly": "npm run compile && npm run test:node-exports && npm run test:node-types && npm run browser-field && rc-np", "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md", "lint:tsc": "tsc -p tsconfig.json --noEmit", "prettier": "prettier --write --ignore-unknown .", "test": "rc-test", "test:node-exports": "node scripts/test-node-exports.js", + "test:node-types": "node scripts/test-node-types.js", "coverage": "father test --coverage", "prepare": "husky", "tsc": "tsc --noEmit" diff --git a/scripts/rewrite-esm-imports.js b/scripts/rewrite-esm-imports.js index 5a4ee4752..111592792 100644 --- a/scripts/rewrite-esm-imports.js +++ b/scripts/rewrite-esm-imports.js @@ -2,19 +2,22 @@ const fs = require('node:fs'); const path = require('node:path'); const esDirectory = path.resolve(__dirname, '../es'); -const moduleSpecifierPattern = /(\b(?:from|import)\s*(?:\(\s*)?)(['"])(\.\.?\/[^'"]+)\2(\s*\)?)/g; +const moduleSpecifierPattern = + /(\b(?:from|import)\s*(?:\(\s*)?)(['"])(\.\.?\/[^'"]+|\.\.?)\2(\s*\)?)/g; fs.writeFileSync(path.join(esDirectory, 'package.json'), '{\n "type": "module"\n}\n'); -function collectJavaScriptFiles(directory) { +function collectModuleFiles(directory) { return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const entryPath = path.join(directory, entry.name); if (entry.isDirectory()) { - return collectJavaScriptFiles(entryPath); + return collectModuleFiles(entryPath); } - return entry.isFile() && entry.name.endsWith('.js') ? [entryPath] : []; + return entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.d.ts')) + ? [entryPath] + : []; }); } @@ -36,7 +39,7 @@ function resolveModuleSpecifier(filePath, specifier) { let rewriteCount = 0; -collectJavaScriptFiles(esDirectory).forEach((filePath) => { +collectModuleFiles(esDirectory).forEach((filePath) => { const source = fs.readFileSync(filePath, 'utf8'); const rewrittenSource = source.replace( moduleSpecifierPattern, diff --git a/scripts/test-node-types.js b/scripts/test-node-types.js new file mode 100644 index 000000000..8b317d57d --- /dev/null +++ b/scripts/test-node-types.js @@ -0,0 +1,54 @@ +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { execFileSync } = require('node:child_process'); + +const packageDirectory = path.resolve(__dirname, '..'); +const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'rc-picker-node-types-')); +const scopedModulesDirectory = path.join(temporaryDirectory, 'node_modules/@rc-component'); + +try { + fs.mkdirSync(scopedModulesDirectory, { recursive: true }); + fs.symlinkSync( + packageDirectory, + path.join(scopedModulesDirectory, 'picker'), + process.platform === 'win32' ? 'junction' : 'dir', + ); + + fs.writeFileSync( + path.join(temporaryDirectory, 'package.json'), + JSON.stringify({ private: true, type: 'module' }), + ); + fs.writeFileSync( + path.join(temporaryDirectory, 'tsconfig.json'), + JSON.stringify({ + compilerOptions: { + module: 'NodeNext', + moduleResolution: 'NodeNext', + noEmit: true, + skipLibCheck: false, + strict: true, + }, + include: ['index.ts'], + }), + ); + fs.writeFileSync( + path.join(temporaryDirectory, 'index.ts'), + `import Picker from '@rc-component/picker'; +import momentGenerateConfig from '@rc-component/picker/generate/moment'; +import enUS from '@rc-component/picker/locale/en_US'; + +void Picker; +void momentGenerateConfig; +void enUS; +`, + ); + + execFileSync( + process.execPath, + [path.join(packageDirectory, 'node_modules/typescript/bin/tsc'), '-p', 'tsconfig.json'], + { cwd: temporaryDirectory, stdio: 'inherit' }, + ); +} finally { + fs.rmSync(temporaryDirectory, { recursive: true, force: true }); +} diff --git a/src/PickerPanel/index.tsx b/src/PickerPanel/index.tsx index 51e75516e..ce4cc209a 100644 --- a/src/PickerPanel/index.tsx +++ b/src/PickerPanel/index.tsx @@ -64,7 +64,7 @@ export interface BasePickerPanelProps | 'superPrevIcon' | 'superNextIcon' >, - SharedTimeProps, + Omit, 'defaultValue'>, Pick, 'tabIndex'> { // Style prefixCls?: string;