From 98d22039818a3a3193e396aa1b29e64889b4eeaa Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Sep 2026 14:55:55 +0800 Subject: [PATCH 1/5] fix: support native Node ESM exports --- .fatherrc.ts | 10 + .github/workflows/react-component-ci.yml | 14 + package.json | 483 ++++++++++-------- scripts/README.md | 18 + scripts/test-node-exports.js | 175 +++++++ scripts/update-browser-field.js | 6 +- src/PickerInput/Popup/index.tsx | 5 +- src/PickerInput/Selector/RangeSelector.tsx | 5 +- .../Selector/SingleSelector/MultipleDates.tsx | 5 +- src/PickerPanel/index.tsx | 2 +- src/PickerTrigger/index.tsx | 5 +- src/utils/getDefaultExport.ts | 13 + 12 files changed, 525 insertions(+), 216 deletions(-) create mode 100644 scripts/README.md create mode 100644 scripts/test-node-exports.js create mode 100644 src/utils/getDefaultExport.ts diff --git a/.fatherrc.ts b/.fatherrc.ts index 96268ae1e..037bb40cc 100644 --- a/.fatherrc.ts +++ b/.fatherrc.ts @@ -2,4 +2,14 @@ import { defineConfig } from 'father'; export default defineConfig({ plugins: ['@rc-component/father-plugin'], + esm: { + output: 'es', + platform: 'node', + autoExtension: true, + resolveDepSubpath: true, + }, + cjs: { + output: 'lib', + autoExtension: true, + }, }); diff --git a/.github/workflows/react-component-ci.yml b/.github/workflows/react-component-ci.yml index 36dacae47..dc4bd3498 100644 --- a/.github/workflows/react-component-ci.yml +++ b/.github/workflows/react-component-ci.yml @@ -7,3 +7,17 @@ jobs: uses: react-component/rc-test/.github/workflows/test-utoo.yml@main secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + node-exports: + name: Native Node exports + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm install --legacy-peer-deps + - run: npm run compile + - run: npm run browser-field + - run: npm run test:node-exports diff --git a/package.json b/package.json index 1a43f0619..cfa0d697c 100644 --- a/package.json +++ b/package.json @@ -7,75 +7,140 @@ "react-component", "react-picker" ], - "main": "./lib/index", - "module": "./es/index", - "types": "./es/index.d.ts", + "main": "./lib/index.js", + "module": "./es/index.mjs", + "types": "./lib/index.d.ts", "exports": { ".": { - "types": "./es/index.d.ts", - "import": "./es/index.js", - "require": "./lib/index.js" + "import": { + "types": "./es/index.d.mts", + "default": "./es/index.mjs" + }, + "require": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } }, "./assets/*": "./assets/*", "./generate": { - "types": "./es/generate/index.d.ts", - "import": "./es/generate/index.js", - "require": "./lib/generate/index.js" + "import": { + "types": "./es/generate/index.d.mts", + "default": "./es/generate/index.mjs" + }, + "require": { + "types": "./lib/generate/index.d.ts", + "default": "./lib/generate/index.js" + } }, "./generate/*": { - "types": "./es/generate/*.d.ts", - "import": "./es/generate/*.js", - "require": "./lib/generate/*.js" + "import": { + "types": "./es/generate/*.d.mts", + "default": "./es/generate/*.mjs" + }, + "require": { + "types": "./lib/generate/*.d.ts", + "default": "./lib/generate/*.js" + } }, "./interface": { - "types": "./es/interface.d.ts", - "import": "./es/interface.js", - "require": "./lib/interface.js" + "import": { + "types": "./es/interface.d.mts", + "default": "./es/interface.mjs" + }, + "require": { + "types": "./lib/interface.d.ts", + "default": "./lib/interface.js" + } }, "./locale/*": { - "types": "./es/locale/*.d.ts", - "import": "./es/locale/*.js", - "require": "./lib/locale/*.js" + "import": { + "types": "./es/locale/*.d.mts", + "default": "./es/locale/*.mjs" + }, + "require": { + "types": "./lib/locale/*.d.ts", + "default": "./lib/locale/*.js" + } }, "./lib/generate": { - "types": "./es/generate/index.d.ts", - "import": "./es/generate/index.js", - "require": "./lib/generate/index.js" + "import": { + "types": "./es/generate/index.d.mts", + "default": "./es/generate/index.mjs" + }, + "require": { + "types": "./lib/generate/index.d.ts", + "default": "./lib/generate/index.js" + } }, "./lib/generate/*": { - "types": "./es/generate/*.d.ts", - "import": "./es/generate/*.js", - "require": "./lib/generate/*.js" + "import": { + "types": "./es/generate/*.d.mts", + "default": "./es/generate/*.mjs" + }, + "require": { + "types": "./lib/generate/*.d.ts", + "default": "./lib/generate/*.js" + } }, "./lib/interface": { - "types": "./es/interface.d.ts", - "import": "./es/interface.js", - "require": "./lib/interface.js" + "import": { + "types": "./es/interface.d.mts", + "default": "./es/interface.mjs" + }, + "require": { + "types": "./lib/interface.d.ts", + "default": "./lib/interface.js" + } }, "./lib/locale/*": { - "types": "./es/locale/*.d.ts", - "import": "./es/locale/*.js", - "require": "./lib/locale/*.js" + "import": { + "types": "./es/locale/*.d.mts", + "default": "./es/locale/*.mjs" + }, + "require": { + "types": "./lib/locale/*.d.ts", + "default": "./lib/locale/*.js" + } }, "./es/generate": { - "types": "./es/generate/index.d.ts", - "import": "./es/generate/index.js", - "require": "./lib/generate/index.js" + "import": { + "types": "./es/generate/index.d.mts", + "default": "./es/generate/index.mjs" + }, + "require": { + "types": "./lib/generate/index.d.ts", + "default": "./lib/generate/index.js" + } }, "./es/generate/*": { - "types": "./es/generate/*.d.ts", - "import": "./es/generate/*.js", - "require": "./lib/generate/*.js" + "import": { + "types": "./es/generate/*.d.mts", + "default": "./es/generate/*.mjs" + }, + "require": { + "types": "./lib/generate/*.d.ts", + "default": "./lib/generate/*.js" + } }, "./es/interface": { - "types": "./es/interface.d.ts", - "import": "./es/interface.js", - "require": "./lib/interface.js" + "import": { + "types": "./es/interface.d.mts", + "default": "./es/interface.mjs" + }, + "require": { + "types": "./lib/interface.d.ts", + "default": "./lib/interface.js" + } }, "./es/locale/*": { - "types": "./es/locale/*.d.ts", - "import": "./es/locale/*.js", - "require": "./lib/locale/*.js" + "import": { + "types": "./es/locale/*.d.mts", + "default": "./es/locale/*.mjs" + }, + "require": { + "types": "./lib/locale/*.d.ts", + "default": "./lib/locale/*.js" + } } }, "files": [ @@ -101,14 +166,15 @@ "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 browser-field && npm run test:node-exports && 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", "coverage": "father test --coverage", "prepare": "husky", - "tsc": "tsc --noEmit" + "tsc": "tsc --noEmit", + "test:node-exports": "node scripts/test-node-exports.js" }, "dependencies": { "@rc-component/overflow": "^1.0.0", @@ -118,8 +184,8 @@ "clsx": "^2.1.1" }, "devDependencies": { - "@eslint/js": "^10.0.1", "@eslint/compat": "^2.1.0", + "@eslint/js": "^10.0.1", "@rc-component/father-plugin": "^2.2.0", "@rc-component/np": "^1.0.4", "@testing-library/dom": "^10.4.1", @@ -139,7 +205,7 @@ "eslint-plugin-jest": "^29.15.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.1.1", - "father": "^4.6.24", + "father": "^4.6.37", "gh-pages": "^6.3.0", "glob": "^13.0.6", "globals": "^17.7.0", @@ -183,172 +249,173 @@ "node": ">=12.x" }, "browser": { - "./generate/dateFns": "./es/generate/dateFns.js", - "./generate/dateFns.js": "./es/generate/dateFns.js", - "./generate/dayjs": "./es/generate/dayjs.js", - "./generate/dayjs.js": "./es/generate/dayjs.js", - "./generate/index": "./es/generate/index.js", - "./generate/index.js": "./es/generate/index.js", - "./generate/luxon": "./es/generate/luxon.js", - "./generate/luxon.js": "./es/generate/luxon.js", - "./generate/moment": "./es/generate/moment.js", - "./generate/moment.js": "./es/generate/moment.js", - "./lib/index.js": "./es/index.js", - "./locale/am_ET": "./es/locale/am_ET.js", - "./locale/am_ET.js": "./es/locale/am_ET.js", - "./locale/ar_EG": "./es/locale/ar_EG.js", - "./locale/ar_EG.js": "./es/locale/ar_EG.js", - "./locale/az_AZ": "./es/locale/az_AZ.js", - "./locale/az_AZ.js": "./es/locale/az_AZ.js", - "./locale/bg_BG": "./es/locale/bg_BG.js", - "./locale/bg_BG.js": "./es/locale/bg_BG.js", - "./locale/bn_BD": "./es/locale/bn_BD.js", - "./locale/bn_BD.js": "./es/locale/bn_BD.js", - "./locale/by_BY": "./es/locale/by_BY.js", - "./locale/by_BY.js": "./es/locale/by_BY.js", - "./locale/ca_ES": "./es/locale/ca_ES.js", - "./locale/ca_ES.js": "./es/locale/ca_ES.js", - "./locale/common": "./es/locale/common.js", - "./locale/common.js": "./es/locale/common.js", - "./locale/cs_CZ": "./es/locale/cs_CZ.js", - "./locale/cs_CZ.js": "./es/locale/cs_CZ.js", - "./locale/da_DK": "./es/locale/da_DK.js", - "./locale/da_DK.js": "./es/locale/da_DK.js", - "./locale/de_DE": "./es/locale/de_DE.js", - "./locale/de_DE.js": "./es/locale/de_DE.js", - "./locale/el_GR": "./es/locale/el_GR.js", - "./locale/el_GR.js": "./es/locale/el_GR.js", - "./locale/en_GB": "./es/locale/en_GB.js", - "./locale/en_GB.js": "./es/locale/en_GB.js", - "./locale/en_US": "./es/locale/en_US.js", - "./locale/en_US.js": "./es/locale/en_US.js", - "./locale/es_ES": "./es/locale/es_ES.js", - "./locale/es_ES.js": "./es/locale/es_ES.js", - "./locale/es_MX": "./es/locale/es_MX.js", - "./locale/es_MX.js": "./es/locale/es_MX.js", - "./locale/et_EE": "./es/locale/et_EE.js", - "./locale/et_EE.js": "./es/locale/et_EE.js", - "./locale/eu_ES": "./es/locale/eu_ES.js", - "./locale/eu_ES.js": "./es/locale/eu_ES.js", - "./locale/fa_IR": "./es/locale/fa_IR.js", - "./locale/fa_IR.js": "./es/locale/fa_IR.js", - "./locale/fi_FI": "./es/locale/fi_FI.js", - "./locale/fi_FI.js": "./es/locale/fi_FI.js", - "./locale/fr_BE": "./es/locale/fr_BE.js", - "./locale/fr_BE.js": "./es/locale/fr_BE.js", - "./locale/fr_CA": "./es/locale/fr_CA.js", - "./locale/fr_CA.js": "./es/locale/fr_CA.js", - "./locale/fr_FR": "./es/locale/fr_FR.js", - "./locale/fr_FR.js": "./es/locale/fr_FR.js", - "./locale/ga_IE": "./es/locale/ga_IE.js", - "./locale/ga_IE.js": "./es/locale/ga_IE.js", - "./locale/gl_ES": "./es/locale/gl_ES.js", - "./locale/gl_ES.js": "./es/locale/gl_ES.js", - "./locale/he_IL": "./es/locale/he_IL.js", - "./locale/he_IL.js": "./es/locale/he_IL.js", - "./locale/hi_IN": "./es/locale/hi_IN.js", - "./locale/hi_IN.js": "./es/locale/hi_IN.js", - "./locale/hr_HR": "./es/locale/hr_HR.js", - "./locale/hr_HR.js": "./es/locale/hr_HR.js", - "./locale/hu_HU": "./es/locale/hu_HU.js", - "./locale/hu_HU.js": "./es/locale/hu_HU.js", - "./locale/id_ID": "./es/locale/id_ID.js", - "./locale/id_ID.js": "./es/locale/id_ID.js", - "./locale/is_IS": "./es/locale/is_IS.js", - "./locale/is_IS.js": "./es/locale/is_IS.js", - "./locale/it_IT": "./es/locale/it_IT.js", - "./locale/it_IT.js": "./es/locale/it_IT.js", - "./locale/ja_JP": "./es/locale/ja_JP.js", - "./locale/ja_JP.js": "./es/locale/ja_JP.js", - "./locale/ka_GE": "./es/locale/ka_GE.js", - "./locale/ka_GE.js": "./es/locale/ka_GE.js", - "./locale/kk_KZ": "./es/locale/kk_KZ.js", - "./locale/kk_KZ.js": "./es/locale/kk_KZ.js", - "./locale/km_KH": "./es/locale/km_KH.js", - "./locale/km_KH.js": "./es/locale/km_KH.js", - "./locale/kmr_IQ": "./es/locale/kmr_IQ.js", - "./locale/kmr_IQ.js": "./es/locale/kmr_IQ.js", - "./locale/kn_IN": "./es/locale/kn_IN.js", - "./locale/kn_IN.js": "./es/locale/kn_IN.js", - "./locale/ko_KR": "./es/locale/ko_KR.js", - "./locale/ko_KR.js": "./es/locale/ko_KR.js", - "./locale/lt_LT": "./es/locale/lt_LT.js", - "./locale/lt_LT.js": "./es/locale/lt_LT.js", - "./locale/lv_LV": "./es/locale/lv_LV.js", - "./locale/lv_LV.js": "./es/locale/lv_LV.js", - "./locale/mk_MK": "./es/locale/mk_MK.js", - "./locale/mk_MK.js": "./es/locale/mk_MK.js", - "./locale/ml_IN": "./es/locale/ml_IN.js", - "./locale/ml_IN.js": "./es/locale/ml_IN.js", - "./locale/mn_MN": "./es/locale/mn_MN.js", - "./locale/mn_MN.js": "./es/locale/mn_MN.js", - "./locale/mr_IN": "./es/locale/mr_IN.js", - "./locale/mr_IN.js": "./es/locale/mr_IN.js", - "./locale/ms_MY": "./es/locale/ms_MY.js", - "./locale/ms_MY.js": "./es/locale/ms_MY.js", - "./locale/my_MM": "./es/locale/my_MM.js", - "./locale/my_MM.js": "./es/locale/my_MM.js", - "./locale/nb_NO": "./es/locale/nb_NO.js", - "./locale/nb_NO.js": "./es/locale/nb_NO.js", - "./locale/ne_NP": "./es/locale/ne_NP.js", - "./locale/ne_NP.js": "./es/locale/ne_NP.js", - "./locale/nl_BE": "./es/locale/nl_BE.js", - "./locale/nl_BE.js": "./es/locale/nl_BE.js", - "./locale/nl_NL": "./es/locale/nl_NL.js", - "./locale/nl_NL.js": "./es/locale/nl_NL.js", - "./locale/pl_PL": "./es/locale/pl_PL.js", - "./locale/pl_PL.js": "./es/locale/pl_PL.js", - "./locale/pt_BR": "./es/locale/pt_BR.js", - "./locale/pt_BR.js": "./es/locale/pt_BR.js", - "./locale/pt_PT": "./es/locale/pt_PT.js", - "./locale/pt_PT.js": "./es/locale/pt_PT.js", - "./locale/ro_RO": "./es/locale/ro_RO.js", - "./locale/ro_RO.js": "./es/locale/ro_RO.js", - "./locale/ru_RU": "./es/locale/ru_RU.js", - "./locale/ru_RU.js": "./es/locale/ru_RU.js", - "./locale/si_LK": "./es/locale/si_LK.js", - "./locale/si_LK.js": "./es/locale/si_LK.js", - "./locale/sk_SK": "./es/locale/sk_SK.js", - "./locale/sk_SK.js": "./es/locale/sk_SK.js", - "./locale/sl_SI": "./es/locale/sl_SI.js", - "./locale/sl_SI.js": "./es/locale/sl_SI.js", - "./locale/sr_Cyrl_RS": "./es/locale/sr_Cyrl_RS.js", - "./locale/sr_Cyrl_RS.js": "./es/locale/sr_Cyrl_RS.js", - "./locale/sr_RS": "./es/locale/sr_RS.js", - "./locale/sr_RS.js": "./es/locale/sr_RS.js", - "./locale/sv_SE": "./es/locale/sv_SE.js", - "./locale/sv_SE.js": "./es/locale/sv_SE.js", - "./locale/ta_IN": "./es/locale/ta_IN.js", - "./locale/ta_IN.js": "./es/locale/ta_IN.js", - "./locale/te_IN": "./es/locale/te_IN.js", - "./locale/te_IN.js": "./es/locale/te_IN.js", - "./locale/th_TH": "./es/locale/th_TH.js", - "./locale/th_TH.js": "./es/locale/th_TH.js", - "./locale/tk_TK": "./es/locale/tk_TK.js", - "./locale/tk_TK.js": "./es/locale/tk_TK.js", - "./locale/tl_PH": "./es/locale/tl_PH.js", - "./locale/tl_PH.js": "./es/locale/tl_PH.js", - "./locale/tr_TR": "./es/locale/tr_TR.js", - "./locale/tr_TR.js": "./es/locale/tr_TR.js", - "./locale/ug_CN": "./es/locale/ug_CN.js", - "./locale/ug_CN.js": "./es/locale/ug_CN.js", - "./locale/uk_UA": "./es/locale/uk_UA.js", - "./locale/uk_UA.js": "./es/locale/uk_UA.js", - "./locale/ur_PK": "./es/locale/ur_PK.js", - "./locale/ur_PK.js": "./es/locale/ur_PK.js", - "./locale/uz_UZ": "./es/locale/uz_UZ.js", - "./locale/uz_UZ.js": "./es/locale/uz_UZ.js", - "./locale/vi_VN": "./es/locale/vi_VN.js", - "./locale/vi_VN.js": "./es/locale/vi_VN.js", - "./locale/zh_CN": "./es/locale/zh_CN.js", - "./locale/zh_CN.js": "./es/locale/zh_CN.js", - "./locale/zh_TW": "./es/locale/zh_TW.js", - "./locale/zh_TW.js": "./es/locale/zh_TW.js" + "./generate/dateFns": "./es/generate/dateFns.mjs", + "./generate/dateFns.js": "./es/generate/dateFns.mjs", + "./generate/dayjs": "./es/generate/dayjs.mjs", + "./generate/dayjs.js": "./es/generate/dayjs.mjs", + "./generate/index": "./es/generate/index.mjs", + "./generate/index.js": "./es/generate/index.mjs", + "./generate/luxon": "./es/generate/luxon.mjs", + "./generate/luxon.js": "./es/generate/luxon.mjs", + "./generate/moment": "./es/generate/moment.mjs", + "./generate/moment.js": "./es/generate/moment.mjs", + "./lib/index.js": "./es/index.mjs", + "./locale/am_ET": "./es/locale/am_ET.mjs", + "./locale/am_ET.js": "./es/locale/am_ET.mjs", + "./locale/ar_EG": "./es/locale/ar_EG.mjs", + "./locale/ar_EG.js": "./es/locale/ar_EG.mjs", + "./locale/az_AZ": "./es/locale/az_AZ.mjs", + "./locale/az_AZ.js": "./es/locale/az_AZ.mjs", + "./locale/bg_BG": "./es/locale/bg_BG.mjs", + "./locale/bg_BG.js": "./es/locale/bg_BG.mjs", + "./locale/bn_BD": "./es/locale/bn_BD.mjs", + "./locale/bn_BD.js": "./es/locale/bn_BD.mjs", + "./locale/by_BY": "./es/locale/by_BY.mjs", + "./locale/by_BY.js": "./es/locale/by_BY.mjs", + "./locale/ca_ES": "./es/locale/ca_ES.mjs", + "./locale/ca_ES.js": "./es/locale/ca_ES.mjs", + "./locale/common": "./es/locale/common.mjs", + "./locale/common.js": "./es/locale/common.mjs", + "./locale/cs_CZ": "./es/locale/cs_CZ.mjs", + "./locale/cs_CZ.js": "./es/locale/cs_CZ.mjs", + "./locale/da_DK": "./es/locale/da_DK.mjs", + "./locale/da_DK.js": "./es/locale/da_DK.mjs", + "./locale/de_DE": "./es/locale/de_DE.mjs", + "./locale/de_DE.js": "./es/locale/de_DE.mjs", + "./locale/el_GR": "./es/locale/el_GR.mjs", + "./locale/el_GR.js": "./es/locale/el_GR.mjs", + "./locale/en_GB": "./es/locale/en_GB.mjs", + "./locale/en_GB.js": "./es/locale/en_GB.mjs", + "./locale/en_US": "./es/locale/en_US.mjs", + "./locale/en_US.js": "./es/locale/en_US.mjs", + "./locale/es_ES": "./es/locale/es_ES.mjs", + "./locale/es_ES.js": "./es/locale/es_ES.mjs", + "./locale/es_MX": "./es/locale/es_MX.mjs", + "./locale/es_MX.js": "./es/locale/es_MX.mjs", + "./locale/et_EE": "./es/locale/et_EE.mjs", + "./locale/et_EE.js": "./es/locale/et_EE.mjs", + "./locale/eu_ES": "./es/locale/eu_ES.mjs", + "./locale/eu_ES.js": "./es/locale/eu_ES.mjs", + "./locale/fa_IR": "./es/locale/fa_IR.mjs", + "./locale/fa_IR.js": "./es/locale/fa_IR.mjs", + "./locale/fi_FI": "./es/locale/fi_FI.mjs", + "./locale/fi_FI.js": "./es/locale/fi_FI.mjs", + "./locale/fr_BE": "./es/locale/fr_BE.mjs", + "./locale/fr_BE.js": "./es/locale/fr_BE.mjs", + "./locale/fr_CA": "./es/locale/fr_CA.mjs", + "./locale/fr_CA.js": "./es/locale/fr_CA.mjs", + "./locale/fr_FR": "./es/locale/fr_FR.mjs", + "./locale/fr_FR.js": "./es/locale/fr_FR.mjs", + "./locale/ga_IE": "./es/locale/ga_IE.mjs", + "./locale/ga_IE.js": "./es/locale/ga_IE.mjs", + "./locale/gl_ES": "./es/locale/gl_ES.mjs", + "./locale/gl_ES.js": "./es/locale/gl_ES.mjs", + "./locale/he_IL": "./es/locale/he_IL.mjs", + "./locale/he_IL.js": "./es/locale/he_IL.mjs", + "./locale/hi_IN": "./es/locale/hi_IN.mjs", + "./locale/hi_IN.js": "./es/locale/hi_IN.mjs", + "./locale/hr_HR": "./es/locale/hr_HR.mjs", + "./locale/hr_HR.js": "./es/locale/hr_HR.mjs", + "./locale/hu_HU": "./es/locale/hu_HU.mjs", + "./locale/hu_HU.js": "./es/locale/hu_HU.mjs", + "./locale/id_ID": "./es/locale/id_ID.mjs", + "./locale/id_ID.js": "./es/locale/id_ID.mjs", + "./locale/is_IS": "./es/locale/is_IS.mjs", + "./locale/is_IS.js": "./es/locale/is_IS.mjs", + "./locale/it_IT": "./es/locale/it_IT.mjs", + "./locale/it_IT.js": "./es/locale/it_IT.mjs", + "./locale/ja_JP": "./es/locale/ja_JP.mjs", + "./locale/ja_JP.js": "./es/locale/ja_JP.mjs", + "./locale/ka_GE": "./es/locale/ka_GE.mjs", + "./locale/ka_GE.js": "./es/locale/ka_GE.mjs", + "./locale/kk_KZ": "./es/locale/kk_KZ.mjs", + "./locale/kk_KZ.js": "./es/locale/kk_KZ.mjs", + "./locale/km_KH": "./es/locale/km_KH.mjs", + "./locale/km_KH.js": "./es/locale/km_KH.mjs", + "./locale/kmr_IQ": "./es/locale/kmr_IQ.mjs", + "./locale/kmr_IQ.js": "./es/locale/kmr_IQ.mjs", + "./locale/kn_IN": "./es/locale/kn_IN.mjs", + "./locale/kn_IN.js": "./es/locale/kn_IN.mjs", + "./locale/ko_KR": "./es/locale/ko_KR.mjs", + "./locale/ko_KR.js": "./es/locale/ko_KR.mjs", + "./locale/lt_LT": "./es/locale/lt_LT.mjs", + "./locale/lt_LT.js": "./es/locale/lt_LT.mjs", + "./locale/lv_LV": "./es/locale/lv_LV.mjs", + "./locale/lv_LV.js": "./es/locale/lv_LV.mjs", + "./locale/mk_MK": "./es/locale/mk_MK.mjs", + "./locale/mk_MK.js": "./es/locale/mk_MK.mjs", + "./locale/ml_IN": "./es/locale/ml_IN.mjs", + "./locale/ml_IN.js": "./es/locale/ml_IN.mjs", + "./locale/mn_MN": "./es/locale/mn_MN.mjs", + "./locale/mn_MN.js": "./es/locale/mn_MN.mjs", + "./locale/mr_IN": "./es/locale/mr_IN.mjs", + "./locale/mr_IN.js": "./es/locale/mr_IN.mjs", + "./locale/ms_MY": "./es/locale/ms_MY.mjs", + "./locale/ms_MY.js": "./es/locale/ms_MY.mjs", + "./locale/my_MM": "./es/locale/my_MM.mjs", + "./locale/my_MM.js": "./es/locale/my_MM.mjs", + "./locale/nb_NO": "./es/locale/nb_NO.mjs", + "./locale/nb_NO.js": "./es/locale/nb_NO.mjs", + "./locale/ne_NP": "./es/locale/ne_NP.mjs", + "./locale/ne_NP.js": "./es/locale/ne_NP.mjs", + "./locale/nl_BE": "./es/locale/nl_BE.mjs", + "./locale/nl_BE.js": "./es/locale/nl_BE.mjs", + "./locale/nl_NL": "./es/locale/nl_NL.mjs", + "./locale/nl_NL.js": "./es/locale/nl_NL.mjs", + "./locale/pl_PL": "./es/locale/pl_PL.mjs", + "./locale/pl_PL.js": "./es/locale/pl_PL.mjs", + "./locale/pt_BR": "./es/locale/pt_BR.mjs", + "./locale/pt_BR.js": "./es/locale/pt_BR.mjs", + "./locale/pt_PT": "./es/locale/pt_PT.mjs", + "./locale/pt_PT.js": "./es/locale/pt_PT.mjs", + "./locale/ro_RO": "./es/locale/ro_RO.mjs", + "./locale/ro_RO.js": "./es/locale/ro_RO.mjs", + "./locale/ru_RU": "./es/locale/ru_RU.mjs", + "./locale/ru_RU.js": "./es/locale/ru_RU.mjs", + "./locale/si_LK": "./es/locale/si_LK.mjs", + "./locale/si_LK.js": "./es/locale/si_LK.mjs", + "./locale/sk_SK": "./es/locale/sk_SK.mjs", + "./locale/sk_SK.js": "./es/locale/sk_SK.mjs", + "./locale/sl_SI": "./es/locale/sl_SI.mjs", + "./locale/sl_SI.js": "./es/locale/sl_SI.mjs", + "./locale/sr_Cyrl_RS": "./es/locale/sr_Cyrl_RS.mjs", + "./locale/sr_Cyrl_RS.js": "./es/locale/sr_Cyrl_RS.mjs", + "./locale/sr_RS": "./es/locale/sr_RS.mjs", + "./locale/sr_RS.js": "./es/locale/sr_RS.mjs", + "./locale/sv_SE": "./es/locale/sv_SE.mjs", + "./locale/sv_SE.js": "./es/locale/sv_SE.mjs", + "./locale/ta_IN": "./es/locale/ta_IN.mjs", + "./locale/ta_IN.js": "./es/locale/ta_IN.mjs", + "./locale/te_IN": "./es/locale/te_IN.mjs", + "./locale/te_IN.js": "./es/locale/te_IN.mjs", + "./locale/th_TH": "./es/locale/th_TH.mjs", + "./locale/th_TH.js": "./es/locale/th_TH.mjs", + "./locale/tk_TK": "./es/locale/tk_TK.mjs", + "./locale/tk_TK.js": "./es/locale/tk_TK.mjs", + "./locale/tl_PH": "./es/locale/tl_PH.mjs", + "./locale/tl_PH.js": "./es/locale/tl_PH.mjs", + "./locale/tr_TR": "./es/locale/tr_TR.mjs", + "./locale/tr_TR.js": "./es/locale/tr_TR.mjs", + "./locale/ug_CN": "./es/locale/ug_CN.mjs", + "./locale/ug_CN.js": "./es/locale/ug_CN.mjs", + "./locale/uk_UA": "./es/locale/uk_UA.mjs", + "./locale/uk_UA.js": "./es/locale/uk_UA.mjs", + "./locale/ur_PK": "./es/locale/ur_PK.mjs", + "./locale/ur_PK.js": "./es/locale/ur_PK.mjs", + "./locale/uz_UZ": "./es/locale/uz_UZ.mjs", + "./locale/uz_UZ.js": "./es/locale/uz_UZ.mjs", + "./locale/vi_VN": "./es/locale/vi_VN.mjs", + "./locale/vi_VN.js": "./es/locale/vi_VN.mjs", + "./locale/zh_CN": "./es/locale/zh_CN.mjs", + "./locale/zh_CN.js": "./es/locale/zh_CN.mjs", + "./locale/zh_TW": "./es/locale/zh_TW.mjs", + "./locale/zh_TW.js": "./es/locale/zh_TW.mjs" }, "publishConfig": { "access": "public" }, "lint-staged": { "*": "prettier --write --ignore-unknown" - } + }, + "type": "commonjs" } diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..4d1e37c0f --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,18 @@ +# Native Node ESM validation + +The build uses Father 4.6.37 or newer, including the module output support from [umijs/father#869](https://github.com/umijs/father/pull/869). + +```sh +npm install --legacy-peer-deps +npm run compile +npm run browser-field +npm run test:node-exports +``` + +The root package remains CommonJS. Father emits `.mjs` / `.d.mts` under `es` and `.js` / `.d.ts` under `lib`, completing relative references and legacy dependency subpaths such as `dayjs/plugin/weekday`. The public exports select matching runtime and declaration files for `import` and `require`. Existing extensionless public subpaths, including the `/lib` and `/es` compatibility aliases, are retained. + +`test:node-exports` creates an independent temporary consumer and checks every JavaScript export (including all locale and generator wildcard entries) through native Node `import()` and `require()`. It checks representative default values, browser mapping targets, and TypeScript NodeNext ESM and CJS consumers with `strict: true` and `skipLibCheck: false`. It runs before the release command. + +SSR checks cover single, multiple, and range pickers in both module formats. Picker normalizes the default exports of trigger, resize-observer, and overflow, whose published Node entries are still transpiled CommonJS. This keeps that interop detail out of consumer code. The panel's base type also omits the time configuration's `defaultValue`, allowing the panel's own nullable and multiple selection declarations to pass strict checking. + +When checking a packed artifact, build and regenerate the browser mappings first, then use `npm pack --ignore-scripts`. This avoids invoking the interactive release command. Install that tarball into a separate consumer with its peer dependencies and repeat the original issue's imports. The same verification script also accepts the installed package directory as its first argument; that installation must include its runtime and type dependencies. diff --git a/scripts/test-node-exports.js b/scripts/test-node-exports.js new file mode 100644 index 000000000..bac368c30 --- /dev/null +++ b/scripts/test-node-exports.js @@ -0,0 +1,175 @@ +#!/usr/bin/env node +const assert = require('node:assert/strict'); +const { execFileSync } = require('node:child_process'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { createRequire } = require('node:module'); +const { pathToFileURL } = require('node:url'); + +const root = path.resolve(process.argv[2] || path.join(__dirname, '..')); +const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); +const requireFromPackage = createRequire(path.join(root, 'package.json')); + +async function main() { + // Resolve the public exports from an independent consumer, without source aliases. + const consumer = fs.mkdtempSync(path.join(os.tmpdir(), 'picker-node-exports-')); + try { + const modules = path.join(consumer, 'node_modules'); + fs.mkdirSync(path.join(modules, '@rc-component'), { recursive: true }); + fs.symlinkSync(root, path.join(modules, pkg.name), 'junction'); + for (const name of ['react', 'react-dom', 'moment']) { + const directory = path.dirname(requireFromPackage.resolve(`${name}/package.json`)); + fs.symlinkSync(directory, path.join(modules, name), 'junction'); + } + fs.symlinkSync( + path.dirname(path.dirname(requireFromPackage.resolve('@types/react/package.json'))), + path.join(modules, '@types'), + 'junction', + ); + fs.writeFileSync(path.join(consumer, 'package.json'), '{"private":true,"type":"module"}'); + + const entries = []; + for (const [key, entry] of Object.entries(pkg.exports)) { + if (typeof entry === 'string') continue; // CSS / Less assets are not Node modules. + if (!key.includes('*')) { + entries.push(key); + continue; + } + const target = entry.import.default; + const dir = path.dirname(target); + const suffix = path.basename(target).slice(1); + for (const file of fs.readdirSync(path.join(root, dir))) { + if (file.endsWith(suffix)) entries.push(key.replace('*', file.slice(0, -suffix.length))); + } + } + + // An .mjs file makes import resolution start in the consumer package. + fs.writeFileSync( + path.join(consumer, 'import.mjs'), + 'export const load = (name) => import(name);', + ); + const { load } = await import(pathToFileURL(path.join(consumer, 'import.mjs')).href); + const requireFromConsumer = createRequire(path.join(consumer, 'require.cjs')); + for (const entry of entries) { + const name = entry === '.' ? pkg.name : pkg.name + entry.slice(1); + const esm = await load(name); + const cjs = requireFromConsumer(name); + assert.deepEqual( + Object.keys(esm).sort(), + Object.keys(cjs) + .filter((key) => key !== '__esModule') + .sort(), + `${name}: ESM and CommonJS public exports differ`, + ); + if (entry === '.') { + assert.equal(esm.default, esm.Picker); + assert.equal(cjs.default, cjs.Picker); + assert.equal(esm.default.$$typeof, Symbol.for('react.forward_ref')); + assert.equal(cjs.default.$$typeof, Symbol.for('react.forward_ref')); + } else if (entry.includes('/locale/') && esm.default) { + assert.deepEqual(esm.default, cjs.default, `${name}: locale must not be double wrapped`); + if (entry.endsWith('/en_US')) assert.equal(esm.default.locale, 'en_US'); + } else if (entry.includes('/generate/') && !entry.endsWith('/index')) { + assert.equal(typeof esm.default.getNow, 'function', name); + assert.equal(typeof cjs.default.getNow, 'function', name); + assert.equal(esm.default.getYear(esm.default.getFixedDate('2024-01-02')), 2024, name); + assert.equal(cjs.default.getYear(cjs.default.getFixedDate('2024-01-02')), 2024, name); + } + } + + const { createElement } = requireFromConsumer('react'); + const { renderToString } = requireFromConsumer('react-dom/server'); + for (const getModule of [load, requireFromConsumer]) { + const { default: Picker, RangePicker } = await getModule(pkg.name); + const { default: locale } = await getModule(`${pkg.name}/locale/en_US`); + const { default: generateConfig } = await getModule(`${pkg.name}/generate/moment`); + const date = generateConfig.getFixedDate('2024-01-02'); + for (const [Component, props] of [ + [Picker, { value: date }], + [Picker, { multiple: true, value: [date] }], + [RangePicker, { value: [date, date] }], + ]) { + const html = renderToString(createElement(Component, { locale, generateConfig, ...props })); + assert.match(html, /2024-01-02/, 'SSR should render the selected date'); + } + } + + for (const [name, target] of Object.entries(pkg.browser)) { + assert.ok( + fs.existsSync(path.join(root, target)), + `Missing browser target: ${name} -> ${target}`, + ); + } + assert.equal(pkg.browser['./locale/en_US.js'], './es/locale/en_US.mjs'); + assert.equal(pkg.browser['./generate/dayjs'], './es/generate/dayjs.mjs'); + + const source = ` +import { createElement } from 'react'; +import type { Moment } from 'moment'; +import Picker, { type PickerProps, type PickerPanelProps } from '@rc-component/picker'; +import type { GenerateConfig } from '@rc-component/picker/generate'; +import type { Locale } from '@rc-component/picker/interface'; +import moment from '@rc-component/picker/generate/moment'; +import dayjs from '@rc-component/picker/generate/dayjs'; +import dateFns from '@rc-component/picker/generate/dateFns'; +import luxon from '@rc-component/picker/generate/luxon'; +import enUS from '@rc-component/picker/locale/en_US'; +import oldLocale from '@rc-component/picker/lib/locale/en_US'; +import oldESLocale from '@rc-component/picker/es/locale/en_US'; +import oldMoment from '@rc-component/picker/lib/generate/moment'; +import oldESMoment from '@rc-component/picker/es/generate/moment'; +import type { GenerateConfig as OldConfig } from '@rc-component/picker/lib/generate'; +import type { GenerateConfig as OldESConfig } from '@rc-component/picker/es/generate'; +import type { Locale as OldLocale } from '@rc-component/picker/lib/interface'; +import type { Locale as OldESLocale } from '@rc-component/picker/es/interface'; + +const locale: Locale & OldLocale & OldESLocale = enUS; +const locales: Locale[] = [oldLocale, oldESLocale]; +const configs: GenerateConfig[] = [moment, oldMoment, oldESMoment]; +const config: OldConfig & OldESConfig = moment; +const props: PickerProps = { generateConfig: config, locale }; +const emptyPanel: PickerPanelProps = { generateConfig: config, locale, defaultValue: null }; +const multiplePanel: PickerPanelProps = { + generateConfig: config, locale, multiple: true, defaultValue: [moment.getNow()], +}; +createElement(Picker, props); +dayjs.getNow().format('YYYY'); +dateFns.getNow().getFullYear(); +luxon.getNow().toISO(); +void [locales, configs, emptyPanel, multiplePanel]; +`; + for (const extension of ['mts', 'cts']) { + fs.writeFileSync(path.join(consumer, `consumer.${extension}`), source); + } + fs.writeFileSync( + path.join(consumer, 'tsconfig.json'), + JSON.stringify({ + compilerOptions: { + module: 'NodeNext', + moduleResolution: 'NodeNext', + target: 'ES2020', + strict: true, + skipLibCheck: false, + noEmit: true, + esModuleInterop: true, + types: ['node', 'react'], + }, + files: ['consumer.mts', 'consumer.cts'], + }), + ); + execFileSync(process.execPath, [require.resolve('typescript/bin/tsc'), '-p', consumer], { + stdio: 'inherit', + }); + console.log( + `Verified ${entries.length} public imports and requires, SSR, browser targets, and NodeNext ESM/CJS types.`, + ); + } finally { + fs.rmSync(consumer, { recursive: true, force: true }); + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/update-browser-field.js b/scripts/update-browser-field.js index fee8cc97e..9d4c3b965 100644 --- a/scripts/update-browser-field.js +++ b/scripts/update-browser-field.js @@ -14,7 +14,7 @@ const addEntry = (from, to) => { }; // Preserve previous behavior of preferring the ES build for the main entry. -addEntry('./lib/index.js', './es/index.js'); +addEntry('./lib/index.js', './es/index.mjs'); const addDirMappings = (dirPath, browserPrefix, targetPrefix) => { if (!fs.existsSync(dirPath)) { @@ -22,11 +22,11 @@ const addDirMappings = (dirPath, browserPrefix, targetPrefix) => { } fs.readdirSync(dirPath, { withFileTypes: true }) - .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.js')) + .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.mjs')) .map((dirent) => dirent.name) .sort() .forEach((file) => { - const name = path.basename(file, '.js'); + const name = path.basename(file, '.mjs'); const target = `${targetPrefix}/${file}`; addEntry(`${browserPrefix}/${name}`, target); diff --git a/src/PickerInput/Popup/index.tsx b/src/PickerInput/Popup/index.tsx index cb89a9b77..213cdb5f4 100644 --- a/src/PickerInput/Popup/index.tsx +++ b/src/PickerInput/Popup/index.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx'; -import ResizeObserver, { type ResizeObserverProps } from '@rc-component/resize-observer'; +import ResizeObserverModule, { type ResizeObserverProps } from '@rc-component/resize-observer'; import * as React from 'react'; import type { RangeTimeProps, @@ -12,6 +12,9 @@ import PickerContext from '../context'; import Footer, { type FooterProps } from './Footer'; import PopupPanel, { type PopupPanelProps } from './PopupPanel'; import PresetPanel from './PresetPanel'; +import getDefaultExport from '../../utils/getDefaultExport'; + +const ResizeObserver = getDefaultExport(ResizeObserverModule); export type PopupShowTimeConfig = Omit< RangeTimeProps, diff --git a/src/PickerInput/Selector/RangeSelector.tsx b/src/PickerInput/Selector/RangeSelector.tsx index 9a0034548..934d674c5 100644 --- a/src/PickerInput/Selector/RangeSelector.tsx +++ b/src/PickerInput/Selector/RangeSelector.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx'; -import ResizeObserver from '@rc-component/resize-observer'; +import ResizeObserverModule from '@rc-component/resize-observer'; import { isReactRenderable, useEvent } from '@rc-component/util'; import * as React from 'react'; import type { RangePickerRef, SelectorProps } from '../../interface'; @@ -9,6 +9,9 @@ import useRootProps from './hooks/useRootProps'; import Icon from './Icon'; import ClearIcon from './ClearIcon'; import Input, { type InputRef } from './Input'; +import getDefaultExport from '../../utils/getDefaultExport'; + +const ResizeObserver = getDefaultExport(ResizeObserverModule); export type SelectorIdType = | string diff --git a/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx b/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx index 84d731ee3..63a6477ce 100644 --- a/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx +++ b/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx @@ -1,8 +1,11 @@ import { clsx } from 'clsx'; -import Overflow from '@rc-component/overflow'; +import OverflowModule from '@rc-component/overflow'; import * as React from 'react'; import type { MouseEventHandler } from 'react'; import type { CustomTagProps, PickerProps } from '../../SinglePicker'; +import getDefaultExport from '../../../utils/getDefaultExport'; + +const Overflow = getDefaultExport(OverflowModule); export interface MultipleDatesProps extends Pick< PickerProps, 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; diff --git a/src/PickerTrigger/index.tsx b/src/PickerTrigger/index.tsx index 91da6ba7f..67503d3a7 100644 --- a/src/PickerTrigger/index.tsx +++ b/src/PickerTrigger/index.tsx @@ -1,8 +1,11 @@ -import Trigger, { type AlignType, type BuildInPlacements } from '@rc-component/trigger'; +import TriggerModule, { type AlignType, type BuildInPlacements } from '@rc-component/trigger'; import { clsx } from 'clsx'; import * as React from 'react'; import { getRealPlacement } from '../utils/uiUtil'; import PickerContext from '../PickerInput/context'; +import getDefaultExport from '../utils/getDefaultExport'; + +const Trigger = getDefaultExport(TriggerModule); const BUILT_IN_PLACEMENTS = { bottomLeft: { diff --git a/src/utils/getDefaultExport.ts b/src/utils/getDefaultExport.ts new file mode 100644 index 000000000..d7a1b4cb5 --- /dev/null +++ b/src/utils/getDefaultExport.ts @@ -0,0 +1,13 @@ +/** Normalize transpiled CommonJS dependencies when loaded by native Node ESM. */ +export default function getDefaultExport(value: T): T { + if ( + value && + typeof value === 'object' && + '__esModule' in value && + value.__esModule && + 'default' in value + ) { + return value.default as T; + } + return value; +} From a9dfbb5c9bb3f13ad536da4f56ef0efdd97a48fa Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Sep 2026 15:02:29 +0800 Subject: [PATCH 2/5] chore: remove one-off ESM validation scaffolding --- .github/workflows/react-component-ci.yml | 14 -- package.json | 5 +- scripts/README.md | 18 --- scripts/test-node-exports.js | 175 ----------------------- 4 files changed, 2 insertions(+), 210 deletions(-) delete mode 100644 scripts/README.md delete mode 100644 scripts/test-node-exports.js diff --git a/.github/workflows/react-component-ci.yml b/.github/workflows/react-component-ci.yml index dc4bd3498..36dacae47 100644 --- a/.github/workflows/react-component-ci.yml +++ b/.github/workflows/react-component-ci.yml @@ -7,17 +7,3 @@ jobs: uses: react-component/rc-test/.github/workflows/test-utoo.yml@main secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - node-exports: - name: Native Node exports - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: actions/setup-node@v4 - with: - node-version: 22 - - run: npm install --legacy-peer-deps - - run: npm run compile - - run: npm run browser-field - - run: npm run test:node-exports diff --git a/package.json b/package.json index cfa0d697c..b54cfbce9 100644 --- a/package.json +++ b/package.json @@ -166,15 +166,14 @@ "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 && npm run test:node-exports && rc-np", + "prepublishOnly": "npm run compile && 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", "coverage": "father test --coverage", "prepare": "husky", - "tsc": "tsc --noEmit", - "test:node-exports": "node scripts/test-node-exports.js" + "tsc": "tsc --noEmit" }, "dependencies": { "@rc-component/overflow": "^1.0.0", diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 4d1e37c0f..000000000 --- a/scripts/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Native Node ESM validation - -The build uses Father 4.6.37 or newer, including the module output support from [umijs/father#869](https://github.com/umijs/father/pull/869). - -```sh -npm install --legacy-peer-deps -npm run compile -npm run browser-field -npm run test:node-exports -``` - -The root package remains CommonJS. Father emits `.mjs` / `.d.mts` under `es` and `.js` / `.d.ts` under `lib`, completing relative references and legacy dependency subpaths such as `dayjs/plugin/weekday`. The public exports select matching runtime and declaration files for `import` and `require`. Existing extensionless public subpaths, including the `/lib` and `/es` compatibility aliases, are retained. - -`test:node-exports` creates an independent temporary consumer and checks every JavaScript export (including all locale and generator wildcard entries) through native Node `import()` and `require()`. It checks representative default values, browser mapping targets, and TypeScript NodeNext ESM and CJS consumers with `strict: true` and `skipLibCheck: false`. It runs before the release command. - -SSR checks cover single, multiple, and range pickers in both module formats. Picker normalizes the default exports of trigger, resize-observer, and overflow, whose published Node entries are still transpiled CommonJS. This keeps that interop detail out of consumer code. The panel's base type also omits the time configuration's `defaultValue`, allowing the panel's own nullable and multiple selection declarations to pass strict checking. - -When checking a packed artifact, build and regenerate the browser mappings first, then use `npm pack --ignore-scripts`. This avoids invoking the interactive release command. Install that tarball into a separate consumer with its peer dependencies and repeat the original issue's imports. The same verification script also accepts the installed package directory as its first argument; that installation must include its runtime and type dependencies. diff --git a/scripts/test-node-exports.js b/scripts/test-node-exports.js deleted file mode 100644 index bac368c30..000000000 --- a/scripts/test-node-exports.js +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env node -const assert = require('node:assert/strict'); -const { execFileSync } = require('node:child_process'); -const fs = require('node:fs'); -const os = require('node:os'); -const path = require('node:path'); -const { createRequire } = require('node:module'); -const { pathToFileURL } = require('node:url'); - -const root = path.resolve(process.argv[2] || path.join(__dirname, '..')); -const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); -const requireFromPackage = createRequire(path.join(root, 'package.json')); - -async function main() { - // Resolve the public exports from an independent consumer, without source aliases. - const consumer = fs.mkdtempSync(path.join(os.tmpdir(), 'picker-node-exports-')); - try { - const modules = path.join(consumer, 'node_modules'); - fs.mkdirSync(path.join(modules, '@rc-component'), { recursive: true }); - fs.symlinkSync(root, path.join(modules, pkg.name), 'junction'); - for (const name of ['react', 'react-dom', 'moment']) { - const directory = path.dirname(requireFromPackage.resolve(`${name}/package.json`)); - fs.symlinkSync(directory, path.join(modules, name), 'junction'); - } - fs.symlinkSync( - path.dirname(path.dirname(requireFromPackage.resolve('@types/react/package.json'))), - path.join(modules, '@types'), - 'junction', - ); - fs.writeFileSync(path.join(consumer, 'package.json'), '{"private":true,"type":"module"}'); - - const entries = []; - for (const [key, entry] of Object.entries(pkg.exports)) { - if (typeof entry === 'string') continue; // CSS / Less assets are not Node modules. - if (!key.includes('*')) { - entries.push(key); - continue; - } - const target = entry.import.default; - const dir = path.dirname(target); - const suffix = path.basename(target).slice(1); - for (const file of fs.readdirSync(path.join(root, dir))) { - if (file.endsWith(suffix)) entries.push(key.replace('*', file.slice(0, -suffix.length))); - } - } - - // An .mjs file makes import resolution start in the consumer package. - fs.writeFileSync( - path.join(consumer, 'import.mjs'), - 'export const load = (name) => import(name);', - ); - const { load } = await import(pathToFileURL(path.join(consumer, 'import.mjs')).href); - const requireFromConsumer = createRequire(path.join(consumer, 'require.cjs')); - for (const entry of entries) { - const name = entry === '.' ? pkg.name : pkg.name + entry.slice(1); - const esm = await load(name); - const cjs = requireFromConsumer(name); - assert.deepEqual( - Object.keys(esm).sort(), - Object.keys(cjs) - .filter((key) => key !== '__esModule') - .sort(), - `${name}: ESM and CommonJS public exports differ`, - ); - if (entry === '.') { - assert.equal(esm.default, esm.Picker); - assert.equal(cjs.default, cjs.Picker); - assert.equal(esm.default.$$typeof, Symbol.for('react.forward_ref')); - assert.equal(cjs.default.$$typeof, Symbol.for('react.forward_ref')); - } else if (entry.includes('/locale/') && esm.default) { - assert.deepEqual(esm.default, cjs.default, `${name}: locale must not be double wrapped`); - if (entry.endsWith('/en_US')) assert.equal(esm.default.locale, 'en_US'); - } else if (entry.includes('/generate/') && !entry.endsWith('/index')) { - assert.equal(typeof esm.default.getNow, 'function', name); - assert.equal(typeof cjs.default.getNow, 'function', name); - assert.equal(esm.default.getYear(esm.default.getFixedDate('2024-01-02')), 2024, name); - assert.equal(cjs.default.getYear(cjs.default.getFixedDate('2024-01-02')), 2024, name); - } - } - - const { createElement } = requireFromConsumer('react'); - const { renderToString } = requireFromConsumer('react-dom/server'); - for (const getModule of [load, requireFromConsumer]) { - const { default: Picker, RangePicker } = await getModule(pkg.name); - const { default: locale } = await getModule(`${pkg.name}/locale/en_US`); - const { default: generateConfig } = await getModule(`${pkg.name}/generate/moment`); - const date = generateConfig.getFixedDate('2024-01-02'); - for (const [Component, props] of [ - [Picker, { value: date }], - [Picker, { multiple: true, value: [date] }], - [RangePicker, { value: [date, date] }], - ]) { - const html = renderToString(createElement(Component, { locale, generateConfig, ...props })); - assert.match(html, /2024-01-02/, 'SSR should render the selected date'); - } - } - - for (const [name, target] of Object.entries(pkg.browser)) { - assert.ok( - fs.existsSync(path.join(root, target)), - `Missing browser target: ${name} -> ${target}`, - ); - } - assert.equal(pkg.browser['./locale/en_US.js'], './es/locale/en_US.mjs'); - assert.equal(pkg.browser['./generate/dayjs'], './es/generate/dayjs.mjs'); - - const source = ` -import { createElement } from 'react'; -import type { Moment } from 'moment'; -import Picker, { type PickerProps, type PickerPanelProps } from '@rc-component/picker'; -import type { GenerateConfig } from '@rc-component/picker/generate'; -import type { Locale } from '@rc-component/picker/interface'; -import moment from '@rc-component/picker/generate/moment'; -import dayjs from '@rc-component/picker/generate/dayjs'; -import dateFns from '@rc-component/picker/generate/dateFns'; -import luxon from '@rc-component/picker/generate/luxon'; -import enUS from '@rc-component/picker/locale/en_US'; -import oldLocale from '@rc-component/picker/lib/locale/en_US'; -import oldESLocale from '@rc-component/picker/es/locale/en_US'; -import oldMoment from '@rc-component/picker/lib/generate/moment'; -import oldESMoment from '@rc-component/picker/es/generate/moment'; -import type { GenerateConfig as OldConfig } from '@rc-component/picker/lib/generate'; -import type { GenerateConfig as OldESConfig } from '@rc-component/picker/es/generate'; -import type { Locale as OldLocale } from '@rc-component/picker/lib/interface'; -import type { Locale as OldESLocale } from '@rc-component/picker/es/interface'; - -const locale: Locale & OldLocale & OldESLocale = enUS; -const locales: Locale[] = [oldLocale, oldESLocale]; -const configs: GenerateConfig[] = [moment, oldMoment, oldESMoment]; -const config: OldConfig & OldESConfig = moment; -const props: PickerProps = { generateConfig: config, locale }; -const emptyPanel: PickerPanelProps = { generateConfig: config, locale, defaultValue: null }; -const multiplePanel: PickerPanelProps = { - generateConfig: config, locale, multiple: true, defaultValue: [moment.getNow()], -}; -createElement(Picker, props); -dayjs.getNow().format('YYYY'); -dateFns.getNow().getFullYear(); -luxon.getNow().toISO(); -void [locales, configs, emptyPanel, multiplePanel]; -`; - for (const extension of ['mts', 'cts']) { - fs.writeFileSync(path.join(consumer, `consumer.${extension}`), source); - } - fs.writeFileSync( - path.join(consumer, 'tsconfig.json'), - JSON.stringify({ - compilerOptions: { - module: 'NodeNext', - moduleResolution: 'NodeNext', - target: 'ES2020', - strict: true, - skipLibCheck: false, - noEmit: true, - esModuleInterop: true, - types: ['node', 'react'], - }, - files: ['consumer.mts', 'consumer.cts'], - }), - ); - execFileSync(process.execPath, [require.resolve('typescript/bin/tsc'), '-p', consumer], { - stdio: 'inherit', - }); - console.log( - `Verified ${entries.length} public imports and requires, SSR, browser targets, and NodeNext ESM/CJS types.`, - ); - } finally { - fs.rmSync(consumer, { recursive: true, force: true }); - } -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); From 3f48b655ebb403eb9912fb03946a2e604a86232e Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Sep 2026 15:17:19 +0800 Subject: [PATCH 3/5] fix: cover module interop and reuse the preview build --- .github/workflows/surge-preview.yml | 1 + scripts/update-browser-field.js | 3 ++- src/PickerPanel/index.tsx | 1 + tests/util.spec.tsx | 22 ++++++++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/surge-preview.yml b/.github/workflows/surge-preview.yml index d8b2f581a..b3db8753c 100644 --- a/.github/workflows/surge-preview.yml +++ b/.github/workflows/surge-preview.yml @@ -46,6 +46,7 @@ jobs: with: surge_token: ${{ env.SURGE_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} + build: test -d docs-dist dist: docs-dist failOnError: false setCommitStatus: false diff --git a/scripts/update-browser-field.js b/scripts/update-browser-field.js index 9d4c3b965..2a676c5ab 100644 --- a/scripts/update-browser-field.js +++ b/scripts/update-browser-field.js @@ -13,9 +13,10 @@ const addEntry = (from, to) => { browserEntries.push([from, to]); }; -// Preserve previous behavior of preferring the ES build for the main entry. +// Redirect the resolved main file for legacy bundlers that do not use exports. addEntry('./lib/index.js', './es/index.mjs'); +/** Keep legacy browser request names while pointing them at the emitted ESM files. */ const addDirMappings = (dirPath, browserPrefix, targetPrefix) => { if (!fs.existsSync(dirPath)) { return; diff --git a/src/PickerPanel/index.tsx b/src/PickerPanel/index.tsx index ce4cc209a..588d766b0 100644 --- a/src/PickerPanel/index.tsx +++ b/src/PickerPanel/index.tsx @@ -64,6 +64,7 @@ export interface BasePickerPanelProps | 'superPrevIcon' | 'superNextIcon' >, + // Panel values may be null or multiple; the time default only accepts one date. Omit, 'defaultValue'>, Pick, 'tabIndex'> { // Style diff --git a/tests/util.spec.tsx b/tests/util.spec.tsx index dd634116c..d8f5d21c8 100644 --- a/tests/util.spec.tsx +++ b/tests/util.spec.tsx @@ -3,10 +3,32 @@ import dayjs from 'dayjs'; import dayGenerate from '../src/generate/dayjs'; import { isInRange } from '../src/utils/dateUtil'; +import getDefaultExport from '../src/utils/getDefaultExport'; global.error = console.error; describe('Picker.Util', () => { + describe('getDefaultExport', () => { + it('unwraps a transpiled CommonJS namespace loaded by Node ESM', () => { + const component = () => null; + const namespace = { default: component }; + Object.defineProperty(namespace, '__esModule', { value: true }); + expect(getDefaultExport(namespace)).toBe(component); + }); + + it.each([ + null, + undefined, + () => null, + { $$typeof: Symbol.for('react.forward_ref'), render: () => null }, + { default: 'ordinary property' }, + { __esModule: false, default: 'ordinary property' }, + { __esModule: true }, + ])('preserves an already usable export: %p', (value) => { + expect(getDefaultExport(value)).toBe(value); + }); + }); + describe('isInRange', () => { it('not break with null', () => { expect(isInRange(dayGenerate, null, null, dayjs())).toBeFalsy(); From f1046b6e81fb905f1fcbc5a354febebb8cc6b0a1 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Sep 2026 16:08:45 +0800 Subject: [PATCH 4/5] docs: note when to remove CommonJS interop helper --- src/utils/getDefaultExport.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/getDefaultExport.ts b/src/utils/getDefaultExport.ts index d7a1b4cb5..995508bf7 100644 --- a/src/utils/getDefaultExport.ts +++ b/src/utils/getDefaultExport.ts @@ -1,4 +1,7 @@ /** Normalize transpiled CommonJS dependencies when loaded by native Node ESM. */ +// TODO: Remove this helper and its call sites after upgrading @rc-component/trigger, +// @rc-component/resize-observer, and @rc-component/overflow to versions whose native +// ESM entry points work without default-export unwrapping, including their dependencies. export default function getDefaultExport(value: T): T { if ( value && From aceeb4d11162105116690a9bd97c56f166703d83 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 7 Sep 2026 18:23:43 +0800 Subject: [PATCH 5/5] build: use father-plugin for CommonJS default interop --- .fatherrc.ts | 2 ++ package.json | 2 +- src/PickerInput/Popup/index.tsx | 5 +---- src/PickerInput/Selector/RangeSelector.tsx | 5 +---- .../Selector/SingleSelector/MultipleDates.tsx | 5 +---- src/PickerTrigger/index.tsx | 5 +---- src/utils/getDefaultExport.ts | 16 -------------- tests/util.spec.tsx | 22 ------------------- 8 files changed, 7 insertions(+), 55 deletions(-) delete mode 100644 src/utils/getDefaultExport.ts diff --git a/.fatherrc.ts b/.fatherrc.ts index 037bb40cc..e4d7741ae 100644 --- a/.fatherrc.ts +++ b/.fatherrc.ts @@ -1,7 +1,9 @@ +import type {} from '@rc-component/father-plugin'; import { defineConfig } from 'father'; export default defineConfig({ plugins: ['@rc-component/father-plugin'], + cjsDefaultInterop: true, esm: { output: 'es', platform: 'node', diff --git a/package.json b/package.json index b54cfbce9..175db215d 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,7 @@ "devDependencies": { "@eslint/compat": "^2.1.0", "@eslint/js": "^10.0.1", - "@rc-component/father-plugin": "^2.2.0", + "@rc-component/father-plugin": "^2.4.0", "@rc-component/np": "^1.0.4", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", diff --git a/src/PickerInput/Popup/index.tsx b/src/PickerInput/Popup/index.tsx index 213cdb5f4..cb89a9b77 100644 --- a/src/PickerInput/Popup/index.tsx +++ b/src/PickerInput/Popup/index.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx'; -import ResizeObserverModule, { type ResizeObserverProps } from '@rc-component/resize-observer'; +import ResizeObserver, { type ResizeObserverProps } from '@rc-component/resize-observer'; import * as React from 'react'; import type { RangeTimeProps, @@ -12,9 +12,6 @@ import PickerContext from '../context'; import Footer, { type FooterProps } from './Footer'; import PopupPanel, { type PopupPanelProps } from './PopupPanel'; import PresetPanel from './PresetPanel'; -import getDefaultExport from '../../utils/getDefaultExport'; - -const ResizeObserver = getDefaultExport(ResizeObserverModule); export type PopupShowTimeConfig = Omit< RangeTimeProps, diff --git a/src/PickerInput/Selector/RangeSelector.tsx b/src/PickerInput/Selector/RangeSelector.tsx index 934d674c5..9a0034548 100644 --- a/src/PickerInput/Selector/RangeSelector.tsx +++ b/src/PickerInput/Selector/RangeSelector.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx'; -import ResizeObserverModule from '@rc-component/resize-observer'; +import ResizeObserver from '@rc-component/resize-observer'; import { isReactRenderable, useEvent } from '@rc-component/util'; import * as React from 'react'; import type { RangePickerRef, SelectorProps } from '../../interface'; @@ -9,9 +9,6 @@ import useRootProps from './hooks/useRootProps'; import Icon from './Icon'; import ClearIcon from './ClearIcon'; import Input, { type InputRef } from './Input'; -import getDefaultExport from '../../utils/getDefaultExport'; - -const ResizeObserver = getDefaultExport(ResizeObserverModule); export type SelectorIdType = | string diff --git a/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx b/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx index 63a6477ce..84d731ee3 100644 --- a/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx +++ b/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx @@ -1,11 +1,8 @@ import { clsx } from 'clsx'; -import OverflowModule from '@rc-component/overflow'; +import Overflow from '@rc-component/overflow'; import * as React from 'react'; import type { MouseEventHandler } from 'react'; import type { CustomTagProps, PickerProps } from '../../SinglePicker'; -import getDefaultExport from '../../../utils/getDefaultExport'; - -const Overflow = getDefaultExport(OverflowModule); export interface MultipleDatesProps extends Pick< PickerProps, diff --git a/src/PickerTrigger/index.tsx b/src/PickerTrigger/index.tsx index 67503d3a7..91da6ba7f 100644 --- a/src/PickerTrigger/index.tsx +++ b/src/PickerTrigger/index.tsx @@ -1,11 +1,8 @@ -import TriggerModule, { type AlignType, type BuildInPlacements } from '@rc-component/trigger'; +import Trigger, { type AlignType, type BuildInPlacements } from '@rc-component/trigger'; import { clsx } from 'clsx'; import * as React from 'react'; import { getRealPlacement } from '../utils/uiUtil'; import PickerContext from '../PickerInput/context'; -import getDefaultExport from '../utils/getDefaultExport'; - -const Trigger = getDefaultExport(TriggerModule); const BUILT_IN_PLACEMENTS = { bottomLeft: { diff --git a/src/utils/getDefaultExport.ts b/src/utils/getDefaultExport.ts deleted file mode 100644 index 995508bf7..000000000 --- a/src/utils/getDefaultExport.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** Normalize transpiled CommonJS dependencies when loaded by native Node ESM. */ -// TODO: Remove this helper and its call sites after upgrading @rc-component/trigger, -// @rc-component/resize-observer, and @rc-component/overflow to versions whose native -// ESM entry points work without default-export unwrapping, including their dependencies. -export default function getDefaultExport(value: T): T { - if ( - value && - typeof value === 'object' && - '__esModule' in value && - value.__esModule && - 'default' in value - ) { - return value.default as T; - } - return value; -} diff --git a/tests/util.spec.tsx b/tests/util.spec.tsx index d8f5d21c8..dd634116c 100644 --- a/tests/util.spec.tsx +++ b/tests/util.spec.tsx @@ -3,32 +3,10 @@ import dayjs from 'dayjs'; import dayGenerate from '../src/generate/dayjs'; import { isInRange } from '../src/utils/dateUtil'; -import getDefaultExport from '../src/utils/getDefaultExport'; global.error = console.error; describe('Picker.Util', () => { - describe('getDefaultExport', () => { - it('unwraps a transpiled CommonJS namespace loaded by Node ESM', () => { - const component = () => null; - const namespace = { default: component }; - Object.defineProperty(namespace, '__esModule', { value: true }); - expect(getDefaultExport(namespace)).toBe(component); - }); - - it.each([ - null, - undefined, - () => null, - { $$typeof: Symbol.for('react.forward_ref'), render: () => null }, - { default: 'ordinary property' }, - { __esModule: false, default: 'ordinary property' }, - { __esModule: true }, - ])('preserves an already usable export: %p', (value) => { - expect(getDefaultExport(value)).toBe(value); - }); - }); - describe('isInRange', () => { it('not break with null', () => { expect(isInRange(dayGenerate, null, null, dayjs())).toBeFalsy();