[{"data":1,"prerenderedAt":1096},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages":1087},{"id":4,"title":5,"body":6,"description":1066,"extension":1067,"meta":1068,"navigation":1080,"path":1081,"seo":1082,"stem":1085,"__hash__":1086},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002Findex.md","Shipping ESM-Only Packages",{"type":7,"value":8,"toc":1050},"minimark",[9,14,38,45,158,163,212,216,227,243,249,255,316,320,325,484,500,503,507,514,578,586,590,593,597,605,638,749,753,765,769,797,801,865,869,879,892,905,922,926,938,947,964,977,1009,1013,1035,1040,1043,1046],[10,11,13],"h1",{"id":12},"how-to-ship-esm-only-packages-without-breaking-your-users","How to Ship ESM-Only Packages Without Breaking Your Users",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide belongs to ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002F","Modern Module Formats: ESM vs CommonJS",", within ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". For years, library authors published both CommonJS and ES module builds — \"dual packages\" — to serve both Node's ",[30,31,32],"code",{},"require"," and bundlers' ",[30,35,36],{},"import",". That doubled build output, invited the dual package hazard (two copies of a module loaded at once), and kept CommonJS around long after browsers and bundlers moved on.",[15,39,40,41,44],{},"The ecosystem has shifted. Every modern bundler consumes ESM natively and tree-shakes it far better than CommonJS. Node supports ESM fully, and recent Node versions can ",[30,42,43],{},"require()"," synchronous ES modules, removing the biggest historic blocker. For many packages — especially frontend libraries consumed through bundlers — shipping ESM only is now the simpler and faster choice.",[15,46,47],{},[48,49,55,56,55,63,55,67,55,70,55,79,55,85,55,94,55,100,55,105,55,110,55,113,55,116,55,119,55,122,55,125,55,128,55,133,55,137,55,139,55,143,55,145,55,148,55,150,55,153,55,155,55],"svg",{"viewBox":50,"width":51,"role":52,"ariaLabel":53,"style":54},"0 0 760 228","100%","img","Comparison of publishing a library as both CommonJS and ESM with publishing ESM only.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[57,58],"rect",{"className":59,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],"svg-canvas","0","#ffffff",[64,65,66],"title",{},"Dual package vs ESM-only",[68,69,53],"desc",{},[57,71],{"x":72,"y":72,"width":73,"height":74,"rx":75,"fill":76,"stroke":77,"style":78},"1","758","226","10","none","currentColor","stroke-opacity:0.18",[80,81,66],"text",{"x":82,"y":83,"fill":77,"style":84},"28.0","34.0","font-size:16px;font-weight:700",[57,86],{"x":82,"y":87,"width":88,"height":89,"rx":90,"fill":91,"stroke":92,"style":93},"56.0","340.0","150.0","6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[80,95,99],{"x":96,"y":97,"fill":77,"style":98},"42.0","82.0","font-size:14px;font-weight:700","Dual (CJS + ESM)",[80,101,104],{"x":96,"y":102,"fill":77,"style":103},"108.0","font-size:12.5px;font-weight:700","•",[80,106,109],{"x":87,"y":102,"fill":77,"style":107,"textAnchor":108},"font-size:12.5px","start","Two builds, two sets of files",[80,111,104],{"x":96,"y":112,"fill":77,"style":103},"132.0",[80,114,115],{"x":87,"y":112,"fill":77,"style":107,"textAnchor":108},"Conditional exports for import\u002Frequire",[80,117,104],{"x":96,"y":118,"fill":77,"style":103},"156.0",[80,120,121],{"x":87,"y":118,"fill":77,"style":107,"textAnchor":108},"Risk: both copies loaded (hazard)",[80,123,104],{"x":96,"y":124,"fill":77,"style":103},"180.0",[80,126,127],{"x":87,"y":124,"fill":77,"style":107,"textAnchor":108},"CJS path tree-shakes poorly",[57,129],{"x":130,"y":87,"width":88,"height":89,"rx":90,"fill":131,"stroke":131,"style":132},"392.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[80,134,136],{"x":135,"y":97,"fill":77,"style":98},"406.0","ESM-only",[80,138,104],{"x":135,"y":102,"fill":77,"style":103},[80,140,142],{"x":141,"y":102,"fill":77,"style":107,"textAnchor":108},"420.0","One build, one set of files",[80,144,104],{"x":135,"y":112,"fill":77,"style":103},[80,146,147],{"x":141,"y":112,"fill":77,"style":107,"textAnchor":108},"Simple exports map",[80,149,104],{"x":135,"y":118,"fill":77,"style":103},[80,151,152],{"x":141,"y":118,"fill":77,"style":107,"textAnchor":108},"No dual-instance risk",[80,154,104],{"x":135,"y":124,"fill":77,"style":103},[80,156,157],{"x":141,"y":124,"fill":77,"style":107,"textAnchor":108},"Bundlers tree-shake every consumer",[159,160,162],"h2",{"id":161},"rapid-diagnosis","Rapid Diagnosis",[164,165,166,174,190,203],"ul",{},[167,168,169,173],"li",{},[170,171,172],"strong",{},"Check who consumes the package."," Frontend apps through bundlers: ESM-only is straightforward. Node tooling, Jest without ESM support, or old Electron apps: check compatibility first.",[167,175,176,183,184,186,187,189],{},[170,177,178,179,182],{},"Look at your ",[30,180,181],{},"exports"," map."," Separate ",[30,185,36],{}," and ",[30,188,32],{}," conditions pointing at different files indicate a dual package.",[167,191,192,195,196,186,199,202],{},[170,193,194],{},"Search consumer bundles for both builds."," If both ",[30,197,198],{},"dist\u002Findex.cjs",[30,200,201],{},"dist\u002Findex.mjs"," of your package appear in an app's bundle, the dual package hazard is live.",[167,204,205,208,209,211],{},[170,206,207],{},"Check Node version support policy."," Node 20.19+\u002F22.12+ can ",[30,210,43],{}," ES modules without top-level await; older Node versions cannot.",[159,213,215],{"id":214},"root-cause-analysis","Root Cause Analysis",[15,217,218,55,221,186,223,226],{},[170,219,220],{},"1. CommonJS limits static analysis.",[30,222,32],{},[30,224,225],{},"module.exports"," are dynamic; bundlers must keep whole modules, so consumers ship more code.",[15,228,229,232,233,235,236,238,239,242],{},[170,230,231],{},"2. Dual builds create two module instances."," When some code paths ",[30,234,36],{}," and others ",[30,237,32],{}," the same package, two copies load — doubling bytes and breaking singletons (contexts, registries, ",[30,240,241],{},"instanceof"," checks).",[15,244,245,248],{},[170,246,247],{},"3. Interop wrappers add weight."," Bundlers wrap CommonJS modules in interop helpers to emulate ESM semantics, adding bytes and runtime cost.",[15,250,251,254],{},[170,252,253],{},"4. Maintenance burden."," Two outputs, two sets of types and conditional exports mean more ways to publish a broken package.",[15,256,257],{},[48,258,55,261,55,264,55,267,55,269,55,272,55,274,55,281,55,288,55,293,55,297,55,301,55,305,55,307,55,312,55],{"viewBox":259,"width":51,"role":52,"ariaLabel":260,"style":54},"0 0 760 163","Bar chart of bytes a consuming app ships from one utility package when consumed as CommonJS, dual (hazard) and ESM-only.",[57,262],{"className":263,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,265,266],{},"Consumer bundle impact of one package (importing 3 functions)",[68,268,260],{},[57,270],{"x":72,"y":72,"width":73,"height":271,"rx":75,"fill":76,"stroke":77,"style":78},"161",[80,273,266],{"x":82,"y":83,"fill":77,"style":84},[80,275,280],{"x":276,"y":277,"fill":77,"style":278,"textAnchor":279},"179.7","70.0","font-size:13px","end","CommonJS build",[57,282],{"x":283,"y":87,"width":284,"height":285,"rx":286,"fill":91,"stroke":92,"style":287},"191.7","377.8","19","3","fill-opacity:0.7;stroke-opacity:0.9",[80,289,292],{"x":290,"y":277,"fill":77,"style":291},"575.5","font-size:12px;font-weight:600","46KB",[80,294,296],{"x":276,"y":295,"fill":77,"style":278,"textAnchor":279},"101.0","Dual package, both loaded",[57,298],{"x":283,"y":299,"width":300,"height":285,"rx":286,"fill":91,"stroke":92,"style":287},"87.0","476.3",[80,302,304],{"x":303,"y":295,"fill":77,"style":291},"674.0","58KB",[80,306,136],{"x":276,"y":112,"fill":77,"style":278,"textAnchor":279},[57,308],{"x":283,"y":309,"width":310,"height":285,"rx":286,"fill":131,"stroke":131,"style":311},"118.0","57.5","fill-opacity:0.55;stroke-opacity:0.9",[80,313,315],{"x":314,"y":112,"fill":77,"style":291},"255.2","7KB",[159,317,319],{"id":318},"step-by-step-resolution","Step-by-Step Resolution",[321,322,324],"h3",{"id":323},"_1-declare-the-package-as-esm-with-a-clean-exports-map","1. Declare the package as ESM with a clean exports map",[326,327,332],"pre",{"className":328,"code":329,"language":330,"meta":331,"style":331},"language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","{\n  \"name\": \"@acme\u002Futils\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": { \"types\": \".\u002Fdist\u002Findex.d.ts\", \"default\": \".\u002Fdist\u002Findex.js\" },\n    \".\u002Fformat\": { \"types\": \".\u002Fdist\u002Fformat.d.ts\", \"default\": \".\u002Fdist\u002Fformat.js\" }\n  },\n  \"sideEffects\": false,\n  \"engines\": { \"node\": \">=20.19\" }\n}\n","json","",[30,333,334,343,360,373,382,413,440,446,460,478],{"__ignoreMap":331},[335,336,339],"span",{"class":337,"line":338},"line",1,[335,340,342],{"class":341},"saISM","{\n",[335,344,346,350,353,357],{"class":337,"line":345},2,[335,347,349],{"class":348},"sZBmE","  \"name\"",[335,351,352],{"class":341},": ",[335,354,356],{"class":355},"sZ8jY","\"@acme\u002Futils\"",[335,358,359],{"class":341},",\n",[335,361,363,366,368,371],{"class":337,"line":362},3,[335,364,365],{"class":348},"  \"type\"",[335,367,352],{"class":341},[335,369,370],{"class":355},"\"module\"",[335,372,359],{"class":341},[335,374,376,379],{"class":337,"line":375},4,[335,377,378],{"class":348},"  \"exports\"",[335,380,381],{"class":341},": {\n",[335,383,385,388,391,394,396,399,402,405,407,410],{"class":337,"line":384},5,[335,386,387],{"class":348},"    \".\"",[335,389,390],{"class":341},": { ",[335,392,393],{"class":348},"\"types\"",[335,395,352],{"class":341},[335,397,398],{"class":355},"\".\u002Fdist\u002Findex.d.ts\"",[335,400,401],{"class":341},", ",[335,403,404],{"class":348},"\"default\"",[335,406,352],{"class":341},[335,408,409],{"class":355},"\".\u002Fdist\u002Findex.js\"",[335,411,412],{"class":341}," },\n",[335,414,416,419,421,423,425,428,430,432,434,437],{"class":337,"line":415},6,[335,417,418],{"class":348},"    \".\u002Fformat\"",[335,420,390],{"class":341},[335,422,393],{"class":348},[335,424,352],{"class":341},[335,426,427],{"class":355},"\".\u002Fdist\u002Fformat.d.ts\"",[335,429,401],{"class":341},[335,431,404],{"class":348},[335,433,352],{"class":341},[335,435,436],{"class":355},"\".\u002Fdist\u002Fformat.js\"",[335,438,439],{"class":341}," }\n",[335,441,443],{"class":337,"line":442},7,[335,444,445],{"class":341},"  },\n",[335,447,449,452,454,458],{"class":337,"line":448},8,[335,450,451],{"class":348},"  \"sideEffects\"",[335,453,352],{"class":341},[335,455,457],{"class":456},"sPXB4","false",[335,459,359],{"class":341},[335,461,463,466,468,471,473,476],{"class":337,"line":462},9,[335,464,465],{"class":348},"  \"engines\"",[335,467,390],{"class":341},[335,469,470],{"class":348},"\"node\"",[335,472,352],{"class":341},[335,474,475],{"class":355},"\">=20.19\"",[335,477,439],{"class":341},[335,479,481],{"class":337,"line":480},10,[335,482,483],{"class":341},"}\n",[15,485,486,487,490,491,493,494,496,497,499],{},"The ",[30,488,489],{},"engines"," field documents the minimum Node version able to ",[30,492,43],{}," the package; bundler consumers are unaffected. Trade-off: consumers on older Node who ",[30,495,43],{}," the package will get an error and must upgrade or switch to ",[30,498,36],{},".",[15,501,502],{},"Expected outcome: one module format, explicit public entry points, and accurate side-effect information.",[321,504,506],{"id":505},"_2-avoid-top-level-await-in-public-modules","2. Avoid top-level await in public modules",[15,508,509,510,513],{},"Node's ",[30,511,512],{},"require(esm)"," works only for modules without top-level await. Keep TLA out of anything a CommonJS consumer might load.",[326,515,519],{"className":516,"code":517,"language":518,"meta":331,"style":331},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Avoid in library entry points:\n\u002F\u002F const config = await loadConfig();\n\u002F\u002F Prefer an explicit async initialiser:\nexport async function init(options) { \u002F* ... *\u002F }\n\u002F\u002F trade-off: callers must call init() before use. Document it, and throw a\n\u002F\u002F clear error from functions called before initialisation.\n","javascript",[30,520,521,527,532,537,568,573],{"__ignoreMap":331},[335,522,523],{"class":337,"line":338},[335,524,526],{"class":525},"sjfSM","\u002F\u002F Avoid in library entry points:\n",[335,528,529],{"class":337,"line":345},[335,530,531],{"class":525},"\u002F\u002F const config = await loadConfig();\n",[335,533,534],{"class":337,"line":362},[335,535,536],{"class":525},"\u002F\u002F Prefer an explicit async initialiser:\n",[335,538,539,543,546,549,553,556,560,563,566],{"class":337,"line":375},[335,540,542],{"class":541},"sPARh","export",[335,544,545],{"class":541}," async",[335,547,548],{"class":541}," function",[335,550,552],{"class":551},"smZ65"," init",[335,554,555],{"class":341},"(",[335,557,559],{"class":558},"sQw3B","options",[335,561,562],{"class":341},") { ",[335,564,565],{"class":525},"\u002F* ... *\u002F",[335,567,439],{"class":341},[335,569,570],{"class":337,"line":384},[335,571,572],{"class":525},"\u002F\u002F trade-off: callers must call init() before use. Document it, and throw a\n",[335,574,575],{"class":337,"line":415},[335,576,577],{"class":525},"\u002F\u002F clear error from functions called before initialisation.\n",[15,579,580,581,583,584,499],{},"Expected outcome: the package is loadable from both ",[30,582,36],{}," and modern ",[30,585,32],{},[321,587,589],{"id":588},"_3-release-it-as-a-major-version-with-a-migration-note","3. Release it as a major version with a migration note",[15,591,592],{},"Removing CommonJS is a breaking change for some consumers. Publish a major version, document the minimum Node version, and keep the last dual release maintained for critical fixes for a period.",[321,594,596],{"id":595},"_4-verify-in-real-consumers","4. Verify in real consumers",[15,598,599,600,186,602,604],{},"Test the package from a Vite app, a webpack app, a Next.js app and a Node script using both ",[30,601,36],{},[30,603,32],{},". Check that each bundle contains only the ESM files and only the imported functions.",[326,606,610],{"className":607,"code":608,"language":609,"meta":331,"style":331},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Quick Node compatibility check for require(esm).\nnode -e \"const u = require('@acme\u002Futils'); console.log(typeof u.format)\"\n# trade-off: this only proves the entry loads; run your consumer test suites\n# against the new version before announcing the release.\n","bash",[30,611,612,617,628,633],{"__ignoreMap":331},[335,613,614],{"class":337,"line":338},[335,615,616],{"class":525},"# Quick Node compatibility check for require(esm).\n",[335,618,619,622,625],{"class":337,"line":345},[335,620,621],{"class":558},"node",[335,623,624],{"class":456}," -e",[335,626,627],{"class":355}," \"const u = require('@acme\u002Futils'); console.log(typeof u.format)\"\n",[335,629,630],{"class":337,"line":362},[335,631,632],{"class":525},"# trade-off: this only proves the entry loads; run your consumer test suites\n",[335,634,635],{"class":337,"line":375},[335,636,637],{"class":525},"# against the new version before announcing the release.\n",[15,639,640],{},[48,641,55,644,55,647,55,650,55,652,55,655,55,657,55,662,55,668,55,673,55,676,55,680,55,684,55,687,55,691,55,695,55,698,55,702,55,706,55,713,55,717,55,721,55,726,55,729,55,732,55,736,55,739,55,742,55,745,55],{"viewBox":642,"width":51,"role":52,"ariaLabel":643,"style":54},"0 0 760 318","Four steps to move a package from dual publishing to ESM-only safely.",[57,645],{"className":646,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,648,649],{},"ESM-only migration checklist",[68,651,643],{},[57,653],{"x":72,"y":72,"width":73,"height":654,"rx":75,"fill":76,"stroke":77,"style":78},"316",[80,656,649],{"x":82,"y":83,"fill":77,"style":84},[57,658],{"x":659,"y":87,"width":660,"height":661,"rx":90,"fill":131,"stroke":131,"style":132},"72.0","660.0","51.0",[80,663,667],{"x":664,"y":665,"fill":77,"style":666,"textAnchor":108},"86.0","77.0","font-size:13px;font-weight:700","Set type: module and a single exports map",[80,669,672],{"x":664,"y":670,"fill":77,"style":671,"textAnchor":108},"94.0","font-size:12px","types + default conditions, sideEffects declared",[57,674],{"x":659,"y":675,"width":660,"height":661,"rx":90,"fill":131,"stroke":131,"style":132},"119.0",[80,677,679],{"x":664,"y":678,"fill":77,"style":666,"textAnchor":108},"140.0","Remove top-level await from entry points",[80,681,683],{"x":664,"y":682,"fill":77,"style":671,"textAnchor":108},"157.0","Keeps require(esm) working in modern Node",[57,685],{"x":659,"y":686,"width":660,"height":661,"rx":90,"fill":131,"stroke":131,"style":132},"182.0",[80,688,690],{"x":664,"y":689,"fill":77,"style":666,"textAnchor":108},"203.0","Publish as a new major with engines.node",[80,692,694],{"x":664,"y":693,"fill":77,"style":671,"textAnchor":108},"220.0","Migration note for CommonJS-only consumers",[57,696],{"x":659,"y":697,"width":660,"height":661,"rx":90,"fill":131,"stroke":131,"style":132},"245.0",[80,699,701],{"x":664,"y":700,"fill":77,"style":666,"textAnchor":108},"266.0","Verify in bundlers and Node",[80,703,705],{"x":664,"y":704,"fill":77,"style":671,"textAnchor":108},"283.0","Vite, webpack, Next.js, node import and require",[337,707],{"x1":708,"y1":709,"x2":708,"y2":710,"stroke":77,"strokeWidth":711,"style":712},"43.0","95.5","130.5","1.5","stroke-opacity:0.3",[337,714],{"x1":708,"y1":715,"x2":708,"y2":716,"stroke":77,"strokeWidth":711,"style":712},"158.5","193.5",[337,718],{"x1":708,"y1":719,"x2":708,"y2":720,"stroke":77,"strokeWidth":711,"style":712},"221.5","256.5",[722,723],"circle",{"cx":708,"cy":724,"r":725,"fill":131},"81.5","13",[80,727,72],{"x":708,"y":664,"fill":62,"style":666,"textAnchor":728},"middle",[722,730],{"cx":708,"cy":731,"r":725,"fill":131},"144.5",[80,733,735],{"x":708,"y":734,"fill":62,"style":666,"textAnchor":728},"149.0","2",[722,737],{"cx":708,"cy":738,"r":725,"fill":131},"207.5",[80,740,286],{"x":708,"y":741,"fill":62,"style":666,"textAnchor":728},"212.0",[722,743],{"cx":708,"cy":744,"r":725,"fill":131},"270.5",[80,746,748],{"x":708,"y":747,"fill":62,"style":666,"textAnchor":728},"275.0","4",[159,750,752],{"id":751},"verification","Verification",[15,754,755,756,759,760,186,762,764],{},"In consumer applications, check bundle analysis: the package's files should appear once, with only the imported functions. Confirm no ",[30,757,758],{},"__esModule"," interop wrappers for your package. Run the consumers' tests. For Node consumers, confirm both ",[30,761,36],{},[30,763,32],{}," work on the supported Node versions.",[159,766,768],{"id":767},"worked-example-a-shared-component-utilities-package","Worked Example: A Shared Component Utilities Package",[15,770,771,772,186,775,778,779,781,782,784,785,788,789,792,793,796],{},"An internal utilities package published ",[30,773,774],{},"dist\u002Fcjs",[30,776,777],{},"dist\u002Fesm",". A Next.js app imported it from client components (ESM path) while a server-side helper used ",[30,780,32],{}," (CJS path); both copies ended up in the client bundle through a shared module, adding 51KB and breaking a React context because the provider and consumer came from different copies. The team released an ESM-only major with a single ",[30,783,181],{}," entry, removed a top-level ",[30,786,787],{},"await"," from a config loader, and set ",[30,790,791],{},"engines.node"," to ",[30,794,795],{},">=20.19",". Bundle size for consumers dropped by the full duplicate plus 9KB of interop helpers, and the context bug disappeared.",[159,798,800],{"id":799},"common-mistakes","Common Mistakes",[164,802,803,815,828,848],{},[167,804,805,55,811,814],{},[170,806,807,808,499],{},"Forgetting ",[30,809,810],{},"\"type\": \"module\"",[30,812,813],{},".js"," files are then treated as CommonJS by Node, and imports fail.",[167,816,817,824,825,827],{},[170,818,819,820,823],{},"Leaving ",[30,821,822],{},"main"," pointing at a removed CJS file."," Old tooling reads ",[30,826,822],{},"; point it at the ESM entry or remove it.",[167,829,830,837,838,841,842,845,846,499],{},[170,831,832,833,836],{},"Publishing TypeScript declarations without ",[30,834,835],{},"types"," conditions."," Consumers using ",[30,839,840],{},"moduleResolution: \"bundler\""," or ",[30,843,844],{},"\"node16\""," need them in ",[30,847,181],{},[167,849,850,859,860,186,863,499],{},[170,851,852,853,186,856,858],{},"Using ",[30,854,855],{},"__dirname",[30,857,32],{}," in ESM source."," Replace with ",[30,861,862],{},"import.meta.url",[30,864,36],{},[159,866,868],{"id":867},"edge-cases","Edge Cases",[15,870,871,874,875,878],{},[170,872,873],{},"Jest and other CommonJS-first tools."," Older Jest setups transform test code to CommonJS and may fail to load ESM-only dependencies without configuration (",[30,876,877],{},"transformIgnorePatterns"," or native ESM mode). Consumers using such tools may need to update their test configuration; mention it in the migration note.",[15,880,881,884,885,887,888,891],{},[170,882,883],{},"Electron and older Node runtimes."," Applications pinned to older Node versions cannot ",[30,886,43],{}," ESM; they must use dynamic ",[30,889,890],{},"import()",", which is asynchronous. Decide whether those consumers are in scope.",[15,893,894,897,898,901,902,904],{},[170,895,896],{},"JSON imports."," ESM requires import attributes for JSON (",[30,899,900],{},"import data from '.\u002Fdata.json' with { type: 'json' }","); bundlers handle it, but Node consumers need a supporting version. Prefer exporting data from a ",[30,903,813],{}," module.",[15,906,907,910,911,401,914,917,918,499],{},[170,908,909],{},"CDN consumption."," ESM-only packages work well from ESM CDNs (",[30,912,913],{},"esm.sh",[30,915,916],{},"jsDelivr","'s ESM endpoints) and with import maps, as described in ",[19,919,921],{"href":920},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002F","using import maps for unbundled production",[159,923,925],{"id":924},"faq","FAQ",[927,928,931,935],"details",{"className":929},[930],"faq-item",[932,933,934],"summary",{},"Is ESM-only right for every package?",[15,936,937],{},"For browser-focused libraries consumed through bundlers, almost always. For CLI tools and server libraries with many CommonJS consumers on older Node versions, a transition period with dual publishing may still be kinder. The trend is clearly towards ESM-only.",[927,939,941,944],{"className":940},[930],[932,942,943],{},"Does ESM-only improve runtime performance, or only bundle size?",[15,945,946],{},"Mostly bundle size and correctness. Smaller bundles mean less parse and compile work, which does improve startup in consuming apps. The module format itself has little runtime cost difference once bundled.",[927,948,950,953],{"className":949},[930],[932,951,952],{},"What is the dual package hazard exactly?",[15,954,955,956,958,959,963],{},"It is the situation where both the CommonJS and ESM builds of one package load in the same program, creating two instances of its module state. Singletons, caches and ",[30,957,241],{}," checks break, and bytes double. ",[19,960,962],{"href":961},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ffixing-dual-package-hazard-in-a-library\u002F","Fixing dual package hazard in a library"," covers mitigation if you must stay dual.",[927,965,967,970],{"className":966},[930],[932,968,969],{},"Should I bundle my ESM output into one file?",[15,971,972,973,499],{},"For libraries, prefer preserving modules so consumers' bundlers can drop unused files; see ",[19,974,976],{"href":975},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fbuilding-tree-shakeable-libraries-with-vite-library-mode\u002F","building tree-shakeable libraries with Vite library mode",[927,978,980,983],{"className":979},[930],[932,981,982],{},"Do I still need a CommonJS build for TypeScript users?",[15,984,985,986,988,989,992,993,401,996,841,999,1002,1003,1005,1006,1008],{},"No. TypeScript resolves ESM packages through the ",[30,987,181],{}," map with ",[30,990,991],{},"moduleResolution"," set to ",[30,994,995],{},"bundler",[30,997,998],{},"node16",[30,1000,1001],{},"nodenext",". Projects still on the legacy ",[30,1004,621],{}," resolution mode may fail to find types; the fix on their side is updating ",[30,1007,991],{},", which is worth stating in your release notes.",[159,1010,1012],{"id":1011},"related","Related",[164,1014,1015,1022,1029],{},[167,1016,1017,1021],{},[19,1018,1020],{"href":1019},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fwriting-tree-shakeable-library-exports\u002F","Writing tree-shakeable library exports"," — the export design that ESM makes effective.",[167,1023,1024,1028],{},[19,1025,1027],{"href":1026},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost\u002F","Top-level await and module loading cost"," — why TLA matters for loading and interop.",[167,1030,1031,1034],{},[19,1032,1033],{"href":21},"Modern module formats: ESM vs CommonJS"," — the broader comparison.",[1036,1037,1039],"script",{"type":1038},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Ship ESM-Only Packages Without Breaking Your Users\",\n  \"description\": \"When and how to publish packages as ES modules only, the compatibility edges to handle, and the bundle-size benefits for consuming applications.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Declare the package as ESM with a clean exports map\",\n      \"text\": \"The engines field documents the minimum Node version able to require() the package; bundler consumers are unaffected.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Avoid top-level await in public modules\",\n      \"text\": \"Node's require(esm) works only for modules without top-level await.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Release it as a major version with a migration note\",\n      \"text\": \"Removing CommonJS is a breaking change for some consumers.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Verify in real consumers\",\n      \"text\": \"Test the package from a Vite app, a webpack app, a Next.js app and a Node script using both import and require.\"\n    }\n  ]\n}\n",[1036,1041,1042],{"type":1038},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Ship ESM-Only Packages Without Breaking Your Users\",\n  \"description\": \"When and how to publish packages as ES modules only, the compatibility edges to handle, and the bundle-size benefits for consuming applications.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002F\"\n  }\n}\n",[1036,1044,1045],{"type":1038},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"JavaScript Bundle Optimization & Code Splitting\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Modern Module Formats: ESM vs CommonJS\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Shipping ESM-Only Packages\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002F\"\n    }\n  ]\n}\n",[1047,1048,1049],"style",{},"html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":331,"searchDepth":345,"depth":345,"links":1051},[1052,1053,1054,1060,1061,1062,1063,1064,1065],{"id":161,"depth":345,"text":162},{"id":214,"depth":345,"text":215},{"id":318,"depth":345,"text":319,"children":1055},[1056,1057,1058,1059],{"id":323,"depth":362,"text":324},{"id":505,"depth":362,"text":506},{"id":588,"depth":362,"text":589},{"id":595,"depth":362,"text":596},{"id":751,"depth":345,"text":752},{"id":767,"depth":345,"text":768},{"id":799,"depth":345,"text":800},{"id":867,"depth":345,"text":868},{"id":924,"depth":345,"text":925},{"id":1011,"depth":345,"text":1012},"When and how to publish packages as ES modules only, the compatibility edges to handle, and the bundle-size benefits for consuming applications.","md",{"slug":1069,"type":1070,"breadcrumb":1071,"datePublished":1079,"dateModified":1079},"shipping-esm-only-packages","article",[1072,1075,1076,1077],{"name":1073,"url":1074},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1078},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002F","2026-10-06",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages",{"title":1083,"description":1084},"Shipping ESM-Only npm Packages Without Breaking Users","Drop CommonJS output and ship ESM-only packages: configure exports, handle Node require(esm), avoid dual-package hazards, and give consumers better tree shaking.","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002Findex","bhtp-aa3TPdYxqtF0P9_lf2Ijhj-FpmBlY0vOOtU-eM",[1088,1092],{"title":1089,"path":1090,"stem":1091},"Fixing Dual Package Hazard in a Library","\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ffixing-dual-package-hazard-in-a-library","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ffixing-dual-package-hazard-in-a-library\u002Findex",{"title":1093,"path":1094,"stem":1095},"Top-Level Await and Module Loading Cost","\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost\u002Findex",1791308079836]