Skip to content

Commit eba84ef

Browse files
feat: Add ESM (ECMAScript Modules) support with dual CommonJS/ESM builds (#1130)
* Initial plan * Fix TypeScript configuration to resolve build errors Co-authored-by: tiwarishubham635 <[email protected]> * Add ESM (ECMAScript Modules) support with dual CommonJS/ESM builds Co-authored-by: tiwarishubham635 <[email protected]> * chore: run prettier * chore: run prettier for auto-generated files * chore: add verify-esm to make test * chore: remove verify-esm script --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: tiwarishubham635 <[email protected]> Co-authored-by: Shubham <[email protected]> Co-authored-by: Shubham Tiwari <[email protected]>
1 parent 105d47e commit eba84ef

File tree

8 files changed

+516
-4
lines changed

8 files changed

+516
-4
lines changed

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ TypeScript is supported for TypeScript version 2.9 and above.
3838

3939
To make sure the installation was successful, try sending yourself an SMS message, like this:
4040

41+
**CommonJS:**
4142
```js
4243
// Your AccountSID and Auth Token from console.twilio.com
4344
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
@@ -54,6 +55,25 @@ client.messages
5455
.then((message) => console.log(message.sid));
5556
```
5657

58+
**ESM/ES6 Modules:**
59+
```js
60+
// Your AccountSID and Auth Token from console.twilio.com
61+
import twilio from 'twilio';
62+
63+
const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
64+
const authToken = 'your_auth_token';
65+
66+
const client = twilio(accountSid, authToken);
67+
68+
client.messages
69+
.create({
70+
body: 'Hello from twilio-node',
71+
to: '+12345678901', // Text your number
72+
from: '+12345678901', // From a valid Twilio number
73+
})
74+
.then((message) => console.log(message.sid));
75+
```
76+
5777
After a brief delay, you will receive the text message on your phone.
5878

5979
> **Warning**
@@ -80,6 +100,7 @@ If your environment requires SSL decryption, you can set the path to CA bundle i
80100

81101
If you invoke any V2010 operations without specifying an account SID, `twilio-node` will automatically use the `TWILIO_ACCOUNT_SID` value that the client was initialized with. This is useful for when you'd like to, for example, fetch resources for your main account but also your subaccount. See below:
82102

103+
**CommonJS:**
83104
```javascript
84105
// Your Account SID, Subaccount SID Auth Token from console.twilio.com
85106
const accountSid = process.env.TWILIO_ACCOUNT_SID;
@@ -91,10 +112,25 @@ const mainAccountCalls = client.api.v2010.account.calls.list; // SID not specifi
91112
const subaccountCalls = client.api.v2010.account(subaccountSid).calls.list; // SID specified as subaccountSid
92113
```
93114

115+
**ESM/ES6 Modules:**
116+
```javascript
117+
// Your Account SID, Subaccount SID Auth Token from console.twilio.com
118+
import twilio from 'twilio';
119+
120+
const accountSid = process.env.TWILIO_ACCOUNT_SID;
121+
const authToken = process.env.TWILIO_AUTH_TOKEN;
122+
const subaccountSid = process.env.TWILIO_ACCOUNT_SUBACCOUNT_SID;
123+
124+
const client = twilio(accountSid, authToken);
125+
const mainAccountCalls = client.api.v2010.account.calls.list; // SID not specified, so defaults to accountSid
126+
const subaccountCalls = client.api.v2010.account(subaccountSid).calls.list; // SID specified as subaccountSid
127+
```
128+
94129
### Lazy Loading
95130

96131
`twilio-node` supports lazy loading required modules for faster loading time. Lazy loading is enabled by default. To disable lazy loading, simply instantiate the Twilio client with the `lazyLoading` flag set to `false`:
97132

133+
**CommonJS:**
98134
```javascript
99135
// Your Account SID and Auth Token from console.twilio.com
100136
const accountSid = process.env.TWILIO_ACCOUNT_SID;
@@ -105,6 +141,19 @@ const client = require('twilio')(accountSid, authToken, {
105141
});
106142
```
107143

144+
**ESM/ES6 Modules:**
145+
```javascript
146+
// Your Account SID and Auth Token from console.twilio.com
147+
import twilio from 'twilio';
148+
149+
const accountSid = process.env.TWILIO_ACCOUNT_SID;
150+
const authToken = process.env.TWILIO_AUTH_TOKEN;
151+
152+
const client = twilio(accountSid, authToken, {
153+
lazyLoading: false,
154+
});
155+
```
156+
108157
### Enable Auto-Retry with Exponential Backoff
109158

110159
`twilio-node` supports automatic retry with exponential backoff when API requests receive an [Error 429 response](https://support.twilio.com/hc/en-us/articles/360044308153-Twilio-API-response-Error-429-Too-Many-Requests-). This retry with exponential backoff feature is disabled by default. To enable this feature, instantiate the Twilio client with the `autoRetry` flag set to `true`.

examples/example-esm.mjs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Example usage of twilio-node with ESM/ES6 import syntax
2+
import twilio, { Twilio, RestException } from "../lib-esm/index.js";
3+
4+
const accountSid = process.env.TWILIO_ACCOUNT_SID;
5+
const token = process.env.TWILIO_AUTH_TOKEN;
6+
7+
// Uncomment the following line to specify a custom CA bundle for HTTPS requests:
8+
// process.env.TWILIO_CA_BUNDLE = '/path/to/cert.pem';
9+
// You can also set this as a regular environment variable outside of the code
10+
11+
// Option 1: Use default export (convenience function)
12+
const client = twilio(accountSid, token);
13+
14+
// Option 2: Use named export (Twilio class)
15+
// const client = new Twilio(accountSid, token);
16+
17+
let i = 0;
18+
// Callback as second parameter
19+
client.calls.each({
20+
pageSize: 7,
21+
callback: function (call, done) {
22+
console.log(call.sid);
23+
i++;
24+
if (i === 10) {
25+
done();
26+
}
27+
},
28+
done: function (error) {
29+
console.log("je suis fini");
30+
console.log(error);
31+
},
32+
});
33+
34+
// Callback as first parameter
35+
client.calls.each(function (call) {
36+
console.log(call.sid);
37+
});
38+
39+
const from = process.env.FROM_NUMBER;
40+
const to = process.env.TO_NUMBER;
41+
42+
// Send message using callback with RestException handling
43+
client.messages.create(
44+
{
45+
from: from,
46+
to: to,
47+
body: "create using callback",
48+
},
49+
function (err, result) {
50+
if (err) {
51+
if (err instanceof RestException) {
52+
console.log(`Twilio Error ${err.code}: ${err.message}`);
53+
console.log(`Status: ${err.status}`);
54+
console.log(`More info: ${err.moreInfo}`);
55+
} else {
56+
console.log("Other error:", err);
57+
}
58+
return;
59+
}
60+
console.log("Created message using callback");
61+
console.log(result.sid);
62+
}
63+
);
64+
65+
// Send message using promise with RestException handling
66+
const promise = client.messages.create({
67+
from: from,
68+
to: to,
69+
body: "create using promises",
70+
});
71+
72+
promise
73+
.then(function (message) {
74+
console.log("Created message using promises");
75+
console.log(message.sid);
76+
})
77+
.catch(function (error) {
78+
if (error instanceof RestException) {
79+
console.log(`Twilio Error ${error.code}: ${error.message}`);
80+
console.log(`Status: ${error.status}`);
81+
console.log(`More info: ${error.moreInfo}`);
82+
} else {
83+
console.log("Other error:", error);
84+
}
85+
});
86+
87+
// Using async/await syntax (ESM makes this even more natural)
88+
async function sendMessageAsync() {
89+
try {
90+
const message = await client.messages.create({
91+
from: from,
92+
to: to,
93+
body: "create using async/await",
94+
});
95+
console.log("Created message using async/await");
96+
console.log(message.sid);
97+
} catch (error) {
98+
if (error instanceof RestException) {
99+
console.log(`Twilio Error ${error.code}: ${error.message}`);
100+
console.log(`Status: ${error.status}`);
101+
console.log(`More info: ${error.moreInfo}`);
102+
} else {
103+
console.log("Other error:", error);
104+
}
105+
}
106+
}
107+
108+
// Uncomment to run async example
109+
// sendMessageAsync();

lib-esm/index.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// ESM wrapper for twilio-node
2+
import { createRequire } from "module";
3+
const require = createRequire(import.meta.url);
4+
5+
// Import the CommonJS module
6+
const TwilioSDK = require("../lib/index.js");
7+
8+
// Extract all exports from the CJS module
9+
const {
10+
Twilio,
11+
jwt,
12+
twiml,
13+
RequestClient,
14+
RestException,
15+
ClientCredentialProviderBuilder,
16+
OrgsCredentialProviderBuilder,
17+
NoAuthCredentialProvider,
18+
validateBody,
19+
validateRequest,
20+
validateRequestWithBody,
21+
validateExpressRequest,
22+
validateIncomingRequest,
23+
getExpectedBodyHash,
24+
getExpectedTwilioSignature,
25+
webhook,
26+
} = TwilioSDK;
27+
28+
// Export everything as named exports
29+
export {
30+
Twilio,
31+
jwt,
32+
twiml,
33+
RequestClient,
34+
RestException,
35+
ClientCredentialProviderBuilder,
36+
OrgsCredentialProviderBuilder,
37+
NoAuthCredentialProvider,
38+
validateBody,
39+
validateRequest,
40+
validateRequestWithBody,
41+
validateExpressRequest,
42+
validateIncomingRequest,
43+
getExpectedBodyHash,
44+
getExpectedTwilioSignature,
45+
webhook,
46+
};
47+
48+
// Also provide default export for convenience
49+
export default TwilioSDK;

lib-esm/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}

package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,16 @@
4949
"typedoc": "^0.28.11"
5050
},
5151
"scripts": {
52-
"test": "npm run test:javascript && npm run test:typescript",
52+
"test": "npm run test:javascript && npm run test:typescript && npm run test:esm",
5353
"test:javascript": "jest spec --coverage --detectOpenHandles --testPathIgnorePatterns=spec/cluster",
5454
"test:typescript": "tsc --noEmit",
55+
"test:esm": "node scripts/verify-esm.js",
5556
"jshint": "jshint src/rest/** src/base/** src/http/**",
5657
"jscs": "eslint src/base/**/**.js src/http/**/**.js --fix",
5758
"prepublish": "npm run build",
58-
"build": "tsc",
59+
"build": "npm run build:cjs && npm run build:esm && npm run prettier",
60+
"build:cjs": "tsc",
61+
"build:esm": "node scripts/build-esm.js",
5962
"check": "npm run jshint && npm run jscs",
6063
"ci": "npm run test && npm run nsp && npm run prettier-check",
6164
"nsp": "npm audit --production",
@@ -65,11 +68,19 @@
6568
},
6669
"files": [
6770
"lib",
71+
"lib-esm",
6872
"index.js",
6973
"index.d.ts"
7074
],
7175
"main": "./lib",
76+
"module": "./lib-esm/index.js",
7277
"types": "./index.d.ts",
78+
"exports": {
79+
".": {
80+
"import": "./lib-esm/index.js",
81+
"require": "./lib/index.js"
82+
}
83+
},
7384
"engines": {
7485
"node": ">=14.0"
7586
},

scripts/build-esm.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
// Create lib-esm directory
5+
const libEsmDir = path.join(__dirname, "..", "lib-esm");
6+
if (!fs.existsSync(libEsmDir)) {
7+
fs.mkdirSync(libEsmDir);
8+
}
9+
10+
// ESM wrapper for the main entry point
11+
const esmIndexContent = `// ESM wrapper for twilio-node
12+
import { createRequire } from 'module';
13+
const require = createRequire(import.meta.url);
14+
15+
// Import the CommonJS module
16+
const TwilioSDK = require('../lib/index.js');
17+
18+
// Extract all exports from the CJS module
19+
const {
20+
Twilio,
21+
jwt,
22+
twiml,
23+
RequestClient,
24+
RestException,
25+
ClientCredentialProviderBuilder,
26+
OrgsCredentialProviderBuilder,
27+
NoAuthCredentialProvider,
28+
validateBody,
29+
validateRequest,
30+
validateRequestWithBody,
31+
validateExpressRequest,
32+
validateIncomingRequest,
33+
getExpectedBodyHash,
34+
getExpectedTwilioSignature,
35+
webhook
36+
} = TwilioSDK;
37+
38+
// Export everything as named exports
39+
export {
40+
Twilio,
41+
jwt,
42+
twiml,
43+
RequestClient,
44+
RestException,
45+
ClientCredentialProviderBuilder,
46+
OrgsCredentialProviderBuilder,
47+
NoAuthCredentialProvider,
48+
validateBody,
49+
validateRequest,
50+
validateRequestWithBody,
51+
validateExpressRequest,
52+
validateIncomingRequest,
53+
getExpectedBodyHash,
54+
getExpectedTwilioSignature,
55+
webhook
56+
};
57+
58+
// Also provide default export for convenience
59+
export default TwilioSDK;
60+
`;
61+
62+
fs.writeFileSync(path.join(libEsmDir, "index.js"), esmIndexContent);
63+
64+
// Create package.json for ESM directory
65+
const esmPackageJson = {
66+
type: "module",
67+
};
68+
69+
fs.writeFileSync(
70+
path.join(libEsmDir, "package.json"),
71+
JSON.stringify(esmPackageJson, null, 2)
72+
);
73+
74+
console.log("ESM build completed!");
75+
console.log("Generated files:");
76+
console.log("- lib-esm/index.js");
77+
console.log("- lib-esm/package.json");

0 commit comments

Comments
 (0)