|
| 1 | +# Dual ESM/CJS Module Pattern |
| 2 | + |
| 3 | +Modules that need to work in the browser (especially those importing `@bitgo/wasm-utxo`) must provide both ESM and CJS builds. The browser webpack build uses ESM to properly handle async WASM initialization. |
| 4 | + |
| 5 | +## When to Use |
| 6 | + |
| 7 | +Add dual builds to a module if: |
| 8 | +- It imports `@bitgo/wasm-utxo` (required for browser WASM support) |
| 9 | +- It's imported by other modules that need browser support |
| 10 | +- It needs to be bundled for browser use |
| 11 | + |
| 12 | +## How to Convert a Module |
| 13 | + |
| 14 | +### 1. Update package.json |
| 15 | + |
| 16 | +```json |
| 17 | +{ |
| 18 | + "main": "./dist/cjs/src/index.js", |
| 19 | + "module": "./dist/esm/index.js", |
| 20 | + "browser": "./dist/esm/index.js", |
| 21 | + "types": "./dist/cjs/src/index.d.ts", |
| 22 | + "exports": { |
| 23 | + ".": { |
| 24 | + "import": { |
| 25 | + "types": "./dist/esm/index.d.ts", |
| 26 | + "default": "./dist/esm/index.js" |
| 27 | + }, |
| 28 | + "require": { |
| 29 | + "types": "./dist/cjs/src/index.d.ts", |
| 30 | + "default": "./dist/cjs/src/index.js" |
| 31 | + } |
| 32 | + } |
| 33 | + }, |
| 34 | + "files": ["dist/cjs", "dist/esm"], |
| 35 | + "scripts": { |
| 36 | + "build": "npm run build:cjs && npm run build:esm", |
| 37 | + "build:cjs": "yarn tsc --build --incremental --verbose .", |
| 38 | + "build:esm": "yarn tsc --project tsconfig.esm.json" |
| 39 | + } |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +### 2. Update tsconfig.json |
| 44 | + |
| 45 | +Change output directory to `dist/cjs`: |
| 46 | + |
| 47 | +```json |
| 48 | +{ |
| 49 | + "compilerOptions": { |
| 50 | + "outDir": "./dist/cjs", |
| 51 | + "rootDir": "." |
| 52 | + } |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +### 3. Create tsconfig.esm.json |
| 57 | + |
| 58 | +```json |
| 59 | +{ |
| 60 | + "extends": "./tsconfig.json", |
| 61 | + "compilerOptions": { |
| 62 | + "outDir": "./dist/esm", |
| 63 | + "rootDir": "./src", |
| 64 | + "module": "ES2020", |
| 65 | + "target": "ES2020", |
| 66 | + "moduleResolution": "bundler", |
| 67 | + "lib": ["ES2020", "DOM"], |
| 68 | + "declaration": true, |
| 69 | + "declarationMap": true, |
| 70 | + "skipLibCheck": true |
| 71 | + }, |
| 72 | + "include": ["src/**/*"], |
| 73 | + "exclude": ["node_modules", "test", "dist"], |
| 74 | + "references": [] |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +### 4. Add Webpack Alias (if needed for browser bundle) |
| 79 | + |
| 80 | +If your module is imported in the browser bundle and uses `@bitgo/wasm-utxo`, you must add a webpack alias in [`webpack/bitgojs.config.js`](../webpack/bitgojs.config.js): |
| 81 | + |
| 82 | +```javascript |
| 83 | +resolve: { |
| 84 | + alias: { |
| 85 | + '@bitgo/your-module': path.resolve('../your-module/dist/esm/index.js'), |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +**Why aliases are required:** Webpack doesn't automatically use the `browser` or `module` fields when resolving imports from CJS code. While we could use `resolve.conditionNames: ['browser', 'import', ...]` to prioritize ESM, this breaks third-party packages like `@solana/spl-token` and `@bufbuild/protobuf` that have broken ESM builds. The explicit aliases target only our packages. |
| 91 | + |
| 92 | +## Reference Implementations |
| 93 | + |
| 94 | +- [`modules/utxo-core`](../modules/utxo-core) - Full dual build setup |
| 95 | +- [`modules/abstract-utxo`](../modules/abstract-utxo) - Full dual build setup |
| 96 | +- [`modules/utxo-staking`](../modules/utxo-staking) - Full dual build setup |
| 97 | +- [`modules/utxo-ord`](../modules/utxo-ord) - Full dual build setup |
0 commit comments