From aba71b0350d2bff797fc0e716342b62a2aa7e8d9 Mon Sep 17 00:00:00 2001 From: AlexDemzz Date: Sat, 14 Mar 2026 11:17:57 +0100 Subject: [PATCH] fix: CJS/ESM interop for default export with modern bundlers TypeScript compiles `export { useWebSocket as default }` to `Object.defineProperty(exports, "default", ...)`, which puts useWebSocket at exports.default instead of module.exports. Modern bundlers (Vite 8, Next.js, Remix) expect module.exports itself to be the default export for CJS interop, causing "useWebSocket is not a function" errors. This adds a post-build script that rewrites dist/index.js to use the `module.exports = fn` pattern with named exports as properties, and adds an `exports` field to package.json for proper bundler resolution. All import patterns now work correctly: - const useWebSocket = require('react-use-websocket') - import useWebSocket from 'react-use-websocket' - import { useSocketIO } from 'react-use-websocket' - import { default as useWebSocket } from 'react-use-websocket' Closes #280 --- package.json | 10 +++++++- scripts/fix-cjs-interop.js | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 scripts/fix-cjs-interop.js diff --git a/package.json b/package.json index babf933..0c51f75 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,14 @@ "version": "4.13.0", "description": "React Hook for WebSocket communication", "main": "./dist/index.js", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, "files": [ "src", "dist" @@ -10,7 +18,7 @@ "scripts": { "test": "cross-env node_modules/.bin/jest --verbose --no-cache", "clear-dist": "cross-env rm -rf dist/", - "compile": "cross-env node_modules/.bin/tsc -p .", + "compile": "cross-env node_modules/.bin/tsc -p . && node scripts/fix-cjs-interop.js", "push": "cross-env npm run test && npm run clear-dist && npm run compile && npm publish" }, "repository": { diff --git a/scripts/fix-cjs-interop.js b/scripts/fix-cjs-interop.js new file mode 100644 index 0000000..d103743 --- /dev/null +++ b/scripts/fix-cjs-interop.js @@ -0,0 +1,48 @@ +const fs = require('fs'); +const path = require('path'); + +const indexPath = path.join(__dirname, '..', 'dist', 'index.js'); +const source = fs.readFileSync(indexPath, 'utf8'); + +const requirePattern = /var (\w+) = require\("([^"]+)"\);/g; +const requires = []; +let match; +while ((match = requirePattern.exec(source)) !== null) { + requires.push({ varName: match[1], modulePath: match[2] }); +} + +const exportPattern = + /Object\.defineProperty\(exports, "(\w+)", \{ enumerable: true, get: function \(\) \{ return (\w+)\.(\w+); \} \}\);/g; +const namedExports = []; +while ((match = exportPattern.exec(source)) !== null) { + namedExports.push({ + exportName: match[1], + varName: match[2], + propName: match[3], + }); +} + +const defaultExport = namedExports.find((e) => e.exportName === 'default'); +if (!defaultExport) { + console.log('No default export found in dist/index.js, skipping interop fix.'); + process.exit(0); +} + +let output = '"use strict";\n'; + +for (const req of requires) { + output += `var ${req.varName} = require("${req.modulePath}");\n`; +} + +output += `module.exports = ${defaultExport.varName}.${defaultExport.propName};\n`; +output += `module.exports.__esModule = true;\n`; +output += `module.exports.default = ${defaultExport.varName}.${defaultExport.propName};\n`; + +for (const exp of namedExports) { + if (exp.exportName !== 'default') { + output += `module.exports.${exp.exportName} = ${exp.varName}.${exp.propName};\n`; + } +} + +fs.writeFileSync(indexPath, output); +console.log('Fixed CJS/ESM interop for dist/index.js');