[{"data":1,"prerenderedAt":1142},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost":1133},{"id":4,"title":5,"body":6,"description":1114,"extension":1115,"meta":1116,"navigation":487,"path":1128,"seo":1129,"stem":1131,"__hash__":1132},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost\u002Findex.md","Top-Level Await and Module Loading Cost",{"type":7,"value":8,"toc":1098},"minimark",[9,14,38,45,190,195,236,240,246,252,258,264,396,400,405,577,580,584,587,657,660,664,667,773,776,780,783,904,908,914,918,925,929,959,963,969,975,981,987,991,1003,1012,1025,1037,1057,1061,1084,1088,1091,1094],[10,11,13],"h1",{"id":12},"top-level-await-and-its-hidden-module-loading-cost","Top-Level Await and Its Hidden Module Loading Cost",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide examines a language feature with performance consequences, within ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002F","Modern Module Formats: ESM vs CommonJS"," and ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". Top-level await (TLA) lets an ES module ",[30,31,32],"code",{},"await"," at its top level — ",[30,35,36],{},"const config = await fetch('\u002Fconfig.json').then(r => r.json())"," — and makes every module that imports it wait until that promise settles before evaluating. It is convenient, and it silently turns a fetch inside a leaf module into a blocking dependency of the entire application.",[15,39,40,41,44],{},"The danger is distance. A developer adds TLA to a small configuration module; three levels up, the application entry imports something that imports something that imports it. Now the app cannot render until a network request completes. In bundled builds, bundlers must preserve these semantics, which can force them to wrap modules in async functions and disable some optimisations. In Node, a module graph containing TLA cannot be loaded with ",[30,42,43],{},"require()",".",[15,46,47],{},[48,49,55,56,55,63,55,67,55,70,55,88,55,96,55,102,55,110,55,117,55,122,55,125,55,129,55,131,55,134,55,138,55,140,55,146,55,150,55,153,55,156,55,160,55,163,55,172,55,176,55,180,55,184,55],"svg",{"viewBox":50,"width":51,"role":52,"ariaLabel":53,"style":54},"0 0 760 159","100%","img","Chain of module imports from the application entry down to a configuration module with top-level await, showing the whole chain waiting.","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",{},"How one top-level await blocks the app",[68,69,53],"desc",{},[71,72,73],"defs",{},[74,75,82],"marker",{"id":76,"viewBox":77,"refX":78,"refY":79,"markerWidth":80,"markerHeight":80,"orient":81},"fa9d398865","0 0 10 10","9","5","7","auto-start-reverse",[83,84],"path",{"d":85,"fill":86,"style":87},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[57,89],{"x":90,"y":90,"width":91,"height":92,"rx":93,"fill":94,"stroke":86,"style":95},"1","758","157","10","none","stroke-opacity:0.18",[97,98,66],"text",{"x":99,"y":100,"fill":86,"style":101},"28.0","34.0","font-size:16px;font-weight:700",[57,103],{"x":99,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":108,"style":109},"56.0","108.8","55.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[97,111,116],{"x":112,"y":113,"fill":86,"style":114,"textAnchor":115},"82.4","78.0","font-size:13px;font-weight:700","middle","main.js",[97,118,121],{"x":112,"y":119,"fill":86,"style":120,"textAnchor":115},"96.0","font-size:12px","waits",[57,123],{"x":124,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":108,"style":109},"176.8",[97,126,128],{"x":127,"y":113,"fill":86,"style":114,"textAnchor":115},"231.2","App.vue",[97,130,121],{"x":127,"y":119,"fill":86,"style":120,"textAnchor":115},[57,132],{"x":133,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":108,"style":109},"325.6",[97,135,137],{"x":136,"y":113,"fill":86,"style":114,"textAnchor":115},"380.0","api-client.js",[97,139,121],{"x":136,"y":119,"fill":86,"style":120,"textAnchor":115},[57,141],{"x":142,"y":104,"width":105,"height":106,"rx":107,"fill":143,"stroke":144,"style":145},"474.4","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[97,147,149],{"x":148,"y":113,"fill":86,"style":114,"textAnchor":115},"528.8","config.js",[97,151,152],{"x":148,"y":119,"fill":86,"style":120,"textAnchor":115},"await fetch()",[57,154],{"x":155,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":108,"style":109},"623.2",[97,157,159],{"x":158,"y":113,"fill":86,"style":114,"textAnchor":115},"677.6","Network",[97,161,162],{"x":158,"y":119,"fill":86,"style":120,"textAnchor":115},"400ms",[164,165],"line",{"x1":166,"y1":167,"x2":168,"y2":167,"stroke":86,"strokeWidth":169,"style":170,"markerEnd":171},"136.8","83.5","174.8","1.5","stroke-opacity:0.6","url(#fa9d398865)",[164,173],{"x1":174,"y1":167,"x2":175,"y2":167,"stroke":86,"strokeWidth":169,"style":170,"markerEnd":171},"285.6","323.6",[164,177],{"x1":178,"y1":167,"x2":179,"y2":167,"stroke":86,"strokeWidth":169,"style":170,"markerEnd":171},"434.4","472.4",[164,181],{"x1":182,"y1":167,"x2":183,"y2":167,"stroke":86,"strokeWidth":169,"style":170,"markerEnd":171},"583.2","621.2",[97,185,189],{"x":99,"y":186,"fill":187,"style":188},"137.0","#51617a","font-size:12.5px","Every importer up the chain — including the entry — pauses evaluation until the awaited fetch resolves.",[191,192,194],"h2",{"id":193},"rapid-diagnosis","Rapid Diagnosis",[196,197,198,209,215,225],"ul",{},[199,200,201,205,206,208],"li",{},[202,203,204],"strong",{},"Search for TLA:"," grep built and source code for ",[30,207,32],{}," at module scope (bundlers emit warnings for TLA in some targets).",[199,210,211,214],{},[202,212,213],{},"Profile startup."," In a trace, look for a gap between the entry script loading and the first render where the main thread is idle and a network request is in flight.",[199,216,217,220,221,224],{},[202,218,219],{},"Check bundler output."," Rollup\u002FVite wrap affected modules in async functions; webpack's ",[30,222,223],{},"experiments.topLevelAwait"," (default in webpack 5) emits async modules. Their presence marks the affected graph.",[199,226,227,55,230,232,233,44],{},[202,228,229],{},"Check Node compatibility.",[30,231,43],{}," of a graph containing TLA throws ",[30,234,235],{},"ERR_REQUIRE_ASYNC_MODULE",[191,237,239],{"id":238},"root-cause-analysis","Root Cause Analysis",[15,241,242,245],{},[202,243,244],{},"1. Evaluation blocking propagates."," A module with TLA is asynchronous; every importer becomes asynchronous too, all the way to the entry.",[15,247,248,251],{},[202,249,250],{},"2. Network on the critical path."," Awaiting a fetch at module scope adds a full request to the time before rendering — invisible in the code that imports the module.",[15,253,254,257],{},[202,255,256],{},"3. Serialised evaluation."," Sibling modules that await independently can still evaluate in parallel, but a chain of awaiting modules evaluates sequentially.",[15,259,260,263],{},[202,261,262],{},"4. Optimisation barriers."," Async module wrappers can prevent scope hoisting and complicate code splitting in some bundlers.",[15,265,266],{},[48,267,55,270,55,273,55,276,55,278,55,281,55,283,55,288,55,294,55,300,55,304,55,308,55,312,55,316,55,320,55,323,55,326,55,328,55,332,55,336,55,340,55,344,55,350,55,355,55,358,55,362,55,366,55,368,55,372,55,376,55,380,55,384,55,388,55,392,55],{"viewBox":268,"width":51,"role":52,"ariaLabel":269,"style":54},"0 0 760 242","Two timelines of application startup comparing a config module that awaits a fetch at top level with one that initialises lazily.",[57,271],{"className":272,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,274,275],{},"Startup with and without top-level await in a config module",[68,277,269],{},[57,279],{"x":90,"y":90,"width":91,"height":280,"rx":93,"fill":94,"stroke":86,"style":95},"240",[97,282,275],{"x":99,"y":100,"fill":86,"style":101},[97,284,287],{"x":99,"y":285,"fill":86,"style":286},"74.0","font-size:12.5px;font-weight:700","TLA config",[57,289],{"x":290,"y":104,"width":291,"height":292,"rx":293,"fill":108,"stroke":108,"style":109},"113.4","121.2","26.0","3",[97,295,299],{"x":296,"y":297,"fill":86,"style":298,"textAnchor":115},"174.0","73.5","font-size:11.5px","eval",[57,301],{"x":302,"y":104,"width":303,"height":292,"rx":293,"fill":143,"stroke":144,"style":145},"236.1","243.8",[97,305,307],{"x":306,"y":297,"fill":86,"style":298,"textAnchor":115},"358.0","await config fetch",[57,309],{"x":310,"y":104,"width":311,"height":292,"rx":293,"fill":108,"stroke":108,"style":109},"481.4","182.5",[97,313,315],{"x":314,"y":297,"fill":86,"style":298,"textAnchor":115},"572.7","render",[97,317,319],{"x":99,"y":318,"fill":86,"style":286},"112.0","Lazy init",[57,321],{"x":290,"y":322,"width":291,"height":292,"rx":293,"fill":108,"stroke":108,"style":109},"94.0",[97,324,299],{"x":296,"y":325,"fill":86,"style":298,"textAnchor":115},"111.5",[57,327],{"x":302,"y":322,"width":311,"height":292,"rx":293,"fill":108,"stroke":108,"style":109},[97,329,331],{"x":330,"y":325,"fill":86,"style":298,"textAnchor":115},"327.3","render shell",[97,333,335],{"x":99,"y":334,"fill":86,"style":286},"150.0","Config fetch",[57,337],{"x":302,"y":338,"width":303,"height":292,"rx":293,"fill":86,"stroke":86,"style":339},"132.0","fill-opacity:0.06;stroke-opacity:0.4",[97,341,343],{"x":306,"y":342,"fill":86,"style":298,"textAnchor":115},"149.5","in parallel",[164,345],{"x1":346,"y1":347,"x2":348,"y2":347,"stroke":86,"style":349},"112.6","172.0","726.0","stroke-opacity:0.4",[97,351,354],{"x":352,"y":353,"fill":187,"style":298,"textAnchor":115},"123.3","189.0","0ms",[97,356,357],{"x":296,"y":353,"fill":187,"style":298,"textAnchor":115},"100ms",[97,359,361],{"x":360,"y":353,"fill":187,"style":298,"textAnchor":115},"235.3","200ms",[97,363,365],{"x":364,"y":353,"fill":187,"style":298,"textAnchor":115},"296.7","300ms",[97,367,162],{"x":306,"y":353,"fill":187,"style":298,"textAnchor":115},[97,369,371],{"x":370,"y":353,"fill":187,"style":298,"textAnchor":115},"419.3","500ms",[97,373,375],{"x":374,"y":353,"fill":187,"style":298,"textAnchor":115},"480.7","600ms",[97,377,379],{"x":378,"y":353,"fill":187,"style":298,"textAnchor":115},"542.0","700ms",[97,381,383],{"x":382,"y":353,"fill":187,"style":298,"textAnchor":115},"603.3","800ms",[97,385,387],{"x":386,"y":353,"fill":187,"style":298,"textAnchor":115},"664.7","900ms",[97,389,391],{"x":390,"y":353,"fill":187,"style":298,"textAnchor":115},"712.3","1000ms",[97,393,395],{"x":99,"y":394,"fill":187,"style":188},"220.0","With lazy initialisation the shell renders immediately while the config request runs in parallel.",[191,397,399],{"id":398},"step-by-step-resolution","Step-by-Step Resolution",[401,402,404],"h3",{"id":403},"_1-replace-tla-with-explicit-async-initialisation","1. Replace TLA with explicit async initialisation",[406,407,412],"pre",{"className":408,"code":409,"language":410,"meta":411,"style":411},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Before — config.js\nexport const config = await fetch('\u002Fconfig.json').then((r) => r.json());\n\n\u002F\u002F After — config.js\nlet configPromise;\nexport function getConfig() {\n  configPromise ??= fetch('\u002Fconfig.json').then((r) => r.json());\n  return configPromise;\n}\n\u002F\u002F trade-off: consumers must await getConfig() where they need it, which spreads\n\u002F\u002F async handling through the code. That is the honest cost; TLA hid it.\n","javascript","",[30,413,414,422,482,489,495,504,518,551,559,565,571],{"__ignoreMap":411},[415,416,418],"span",{"class":164,"line":417},1,[415,419,421],{"class":420},"sjfSM","\u002F\u002F Before — config.js\n",[415,423,425,429,432,436,439,442,446,450,454,457,460,463,467,470,473,476,479],{"class":164,"line":424},2,[415,426,428],{"class":427},"sPARh","export",[415,430,431],{"class":427}," const",[415,433,435],{"class":434},"sPXB4"," config",[415,437,438],{"class":427}," =",[415,440,441],{"class":427}," await",[415,443,445],{"class":444},"smZ65"," fetch",[415,447,449],{"class":448},"saISM","(",[415,451,453],{"class":452},"sZ8jY","'\u002Fconfig.json'",[415,455,456],{"class":448},").",[415,458,459],{"class":444},"then",[415,461,462],{"class":448},"((",[415,464,466],{"class":465},"sQw3B","r",[415,468,469],{"class":448},") ",[415,471,472],{"class":427},"=>",[415,474,475],{"class":448}," r.",[415,477,478],{"class":444},"json",[415,480,481],{"class":448},"());\n",[415,483,485],{"class":164,"line":484},3,[415,486,488],{"emptyLinePlaceholder":487},true,"\n",[415,490,492],{"class":164,"line":491},4,[415,493,494],{"class":420},"\u002F\u002F After — config.js\n",[415,496,498,501],{"class":164,"line":497},5,[415,499,500],{"class":427},"let",[415,502,503],{"class":448}," configPromise;\n",[415,505,507,509,512,515],{"class":164,"line":506},6,[415,508,428],{"class":427},[415,510,511],{"class":427}," function",[415,513,514],{"class":444}," getConfig",[415,516,517],{"class":448},"() {\n",[415,519,521,524,527,529,531,533,535,537,539,541,543,545,547,549],{"class":164,"line":520},7,[415,522,523],{"class":448},"  configPromise ",[415,525,526],{"class":427},"??=",[415,528,445],{"class":444},[415,530,449],{"class":448},[415,532,453],{"class":452},[415,534,456],{"class":448},[415,536,459],{"class":444},[415,538,462],{"class":448},[415,540,466],{"class":465},[415,542,469],{"class":448},[415,544,472],{"class":427},[415,546,475],{"class":448},[415,548,478],{"class":444},[415,550,481],{"class":448},[415,552,554,557],{"class":164,"line":553},8,[415,555,556],{"class":427},"  return",[415,558,503],{"class":448},[415,560,562],{"class":164,"line":561},9,[415,563,564],{"class":448},"}\n",[415,566,568],{"class":164,"line":567},10,[415,569,570],{"class":420},"\u002F\u002F trade-off: consumers must await getConfig() where they need it, which spreads\n",[415,572,574],{"class":164,"line":573},11,[415,575,576],{"class":420},"\u002F\u002F async handling through the code. That is the honest cost; TLA hid it.\n",[15,578,579],{},"Expected outcome: importing the module no longer blocks evaluation; the request starts when first needed (or eagerly, see next step).",[401,581,583],{"id":582},"_2-start-critical-requests-early-without-blocking","2. Start critical requests early without blocking",[15,585,586],{},"If the data is needed soon, kick off the request at startup but render the shell first.",[406,588,590],{"className":408,"code":589,"language":410,"meta":411,"style":411},"\u002F\u002F main.js\nimport { getConfig } from '.\u002Fconfig.js';\ngetConfig();                                  \u002F\u002F start the request now\ncreateApp(App).mount('#app');                 \u002F\u002F render without waiting\n\u002F\u002F trade-off: components that need config must handle a loading state. Inline\n\u002F\u002F the config into the HTML instead if it is small and known at render time.\n",[30,591,592,597,614,625,647,652],{"__ignoreMap":411},[415,593,594],{"class":164,"line":417},[415,595,596],{"class":420},"\u002F\u002F main.js\n",[415,598,599,602,605,608,611],{"class":164,"line":424},[415,600,601],{"class":427},"import",[415,603,604],{"class":448}," { getConfig } ",[415,606,607],{"class":427},"from",[415,609,610],{"class":452}," '.\u002Fconfig.js'",[415,612,613],{"class":448},";\n",[415,615,616,619,622],{"class":164,"line":484},[415,617,618],{"class":444},"getConfig",[415,620,621],{"class":448},"();                                  ",[415,623,624],{"class":420},"\u002F\u002F start the request now\n",[415,626,627,630,633,636,638,641,644],{"class":164,"line":491},[415,628,629],{"class":444},"createApp",[415,631,632],{"class":448},"(App).",[415,634,635],{"class":444},"mount",[415,637,449],{"class":448},[415,639,640],{"class":452},"'#app'",[415,642,643],{"class":448},");                 ",[415,645,646],{"class":420},"\u002F\u002F render without waiting\n",[415,648,649],{"class":164,"line":497},[415,650,651],{"class":420},"\u002F\u002F trade-off: components that need config must handle a loading state. Inline\n",[415,653,654],{"class":164,"line":506},[415,655,656],{"class":420},"\u002F\u002F the config into the HTML instead if it is small and known at render time.\n",[15,658,659],{},"Expected outcome: rendering and the request overlap.",[401,661,663],{"id":662},"_3-inline-small-configuration-into-the-html","3. Inline small configuration into the HTML",[15,665,666],{},"For configuration known on the server, embed it as JSON in the page and read it synchronously.",[406,668,672],{"className":669,"code":670,"language":671,"meta":411,"style":411},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cscript type=\"application\u002Fjson\" id=\"app-config\">{\"apiBase\":\"\u002Fapi\",\"flags\":{\"newCheckout\":true}}\u003C\u002Fscript>\n\u003Cscript type=\"module\">\n  const config = JSON.parse(document.getElementById('app-config').textContent);\n  \u002F\u002F trade-off: inlined config is re-sent with every page and not cached\n  \u002F\u002F separately. Keep it small; fetch large or user-specific data instead.\n\u003C\u002Fscript>\n","html",[30,673,674,708,723,754,759,764],{"__ignoreMap":411},[415,675,676,679,683,686,689,692,695,697,700,703,705],{"class":164,"line":417},[415,677,678],{"class":448},"\u003C",[415,680,682],{"class":681},"sZBmE","script",[415,684,685],{"class":434}," type",[415,687,688],{"class":448},"=",[415,690,691],{"class":452},"\"application\u002Fjson\"",[415,693,694],{"class":434}," id",[415,696,688],{"class":448},[415,698,699],{"class":452},"\"app-config\"",[415,701,702],{"class":448},">{\"apiBase\":\"\u002Fapi\",\"flags\":{\"newCheckout\":true}}\u003C\u002F",[415,704,682],{"class":681},[415,706,707],{"class":448},">\n",[415,709,710,712,714,716,718,721],{"class":164,"line":424},[415,711,678],{"class":448},[415,713,682],{"class":681},[415,715,685],{"class":434},[415,717,688],{"class":448},[415,719,720],{"class":452},"\"module\"",[415,722,707],{"class":448},[415,724,725,728,730,732,735,737,740,743,746,748,751],{"class":164,"line":484},[415,726,727],{"class":427},"  const",[415,729,435],{"class":434},[415,731,438],{"class":427},[415,733,734],{"class":434}," JSON",[415,736,44],{"class":448},[415,738,739],{"class":444},"parse",[415,741,742],{"class":448},"(document.",[415,744,745],{"class":444},"getElementById",[415,747,449],{"class":448},[415,749,750],{"class":452},"'app-config'",[415,752,753],{"class":448},").textContent);\n",[415,755,756],{"class":164,"line":491},[415,757,758],{"class":420},"  \u002F\u002F trade-off: inlined config is re-sent with every page and not cached\n",[415,760,761],{"class":164,"line":497},[415,762,763],{"class":420},"  \u002F\u002F separately. Keep it small; fetch large or user-specific data instead.\n",[415,765,766,769,771],{"class":164,"line":506},[415,767,768],{"class":448},"\u003C\u002F",[415,770,682],{"class":681},[415,772,707],{"class":448},[15,774,775],{},"Expected outcome: no request at all on the critical path.",[401,777,779],{"id":778},"_4-keep-tla-for-genuine-module-level-dependencies-only","4. Keep TLA for genuine module-level dependencies only",[15,781,782],{},"TLA is reasonable for modules that cannot function without asynchronous setup and are loaded lazily anyway — a WebAssembly module behind a dynamic import, for example — where blocking only affects that lazy chunk.",[15,784,785],{},[48,786,55,789,55,792,55,795,55,797,55,800,55,802,55,806,55,812,55,816,55,820,55,823,55,827,55,832,55,836,55,840,55,842,55,846,55,848,55,851,55,853,55,857,55,859,55,861,55,863,55,866,55,869,55,873,55,877,55,879,55,883,55,885,55,888,55,891,55,895,55,897,55,899,55,901,55],{"viewBox":787,"width":51,"role":52,"ariaLabel":788,"style":54},"0 0 760 260","Situations where top-level await is acceptable versus where it harms loading performance.",[57,790],{"className":791,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,793,794],{},"Where top-level await is and is not appropriate",[68,796,788],{},[57,798],{"x":90,"y":90,"width":91,"height":799,"rx":93,"fill":94,"stroke":86,"style":95},"258",[97,801,794],{"x":99,"y":100,"fill":86,"style":101},[57,803],{"x":99,"y":104,"width":804,"height":805,"rx":61,"fill":86,"stroke":86,"style":339},"210.0","30.0",[97,807,811],{"x":808,"y":809,"fill":86,"style":286,"textAnchor":810},"38.0","75.5","start","Situation",[57,813],{"x":814,"y":104,"width":815,"height":805,"rx":61,"fill":86,"stroke":86,"style":339},"238.0","247.0",[97,817,819],{"x":818,"y":809,"fill":86,"style":286,"textAnchor":115},"361.5","TLA acceptable?",[57,821],{"x":822,"y":104,"width":815,"height":805,"rx":61,"fill":86,"stroke":86,"style":339},"485.0",[97,824,826],{"x":825,"y":809,"fill":86,"style":286,"textAnchor":115},"608.5","Why",[57,828],{"x":99,"y":829,"width":804,"height":830,"rx":61,"fill":94,"stroke":86,"style":831},"86.0","46.0","stroke-opacity:0.35",[97,833,835],{"x":808,"y":834,"fill":86,"style":286,"textAnchor":810},"105.5","Config fetch imported by the app",[97,837,839],{"x":808,"y":838,"fill":86,"style":286,"textAnchor":810},"121.5","entry",[57,841],{"x":814,"y":829,"width":815,"height":830,"rx":61,"fill":143,"stroke":144,"style":145},[97,843,845],{"x":818,"y":844,"fill":86,"style":120,"textAnchor":115},"113.5","no",[57,847],{"x":822,"y":829,"width":815,"height":830,"rx":61,"fill":94,"stroke":86,"style":831},[97,849,850],{"x":825,"y":844,"fill":86,"style":120,"textAnchor":115},"Blocks first render",[57,852],{"x":99,"y":338,"width":804,"height":805,"rx":61,"fill":94,"stroke":86,"style":831},[97,854,856],{"x":808,"y":855,"fill":86,"style":286,"textAnchor":810},"151.5","Library entry point",[57,858],{"x":814,"y":338,"width":815,"height":805,"rx":61,"fill":143,"stroke":144,"style":145},[97,860,845],{"x":818,"y":855,"fill":86,"style":120,"textAnchor":115},[57,862],{"x":822,"y":338,"width":815,"height":805,"rx":61,"fill":94,"stroke":86,"style":831},[97,864,865],{"x":825,"y":855,"fill":86,"style":120,"textAnchor":115},"Breaks require(esm), blocks consumers",[57,867],{"x":99,"y":868,"width":804,"height":830,"rx":61,"fill":94,"stroke":86,"style":831},"162.0",[97,870,872],{"x":808,"y":871,"fill":86,"style":286,"textAnchor":810},"181.5","Lazy chunk initialising",[97,874,876],{"x":808,"y":875,"fill":86,"style":286,"textAnchor":810},"197.5","WebAssembly",[57,878],{"x":814,"y":868,"width":815,"height":830,"rx":61,"fill":108,"stroke":108,"style":109},[97,880,882],{"x":818,"y":881,"fill":86,"style":120,"textAnchor":115},"189.5","yes",[57,884],{"x":822,"y":868,"width":815,"height":830,"rx":61,"fill":94,"stroke":86,"style":831},[97,886,887],{"x":825,"y":881,"fill":86,"style":120,"textAnchor":115},"Only blocks that lazy feature",[57,889],{"x":99,"y":890,"width":804,"height":805,"rx":61,"fill":94,"stroke":86,"style":831},"208.0",[97,892,894],{"x":808,"y":893,"fill":86,"style":286,"textAnchor":810},"227.5","Scripts and tooling in Node",[57,896],{"x":814,"y":890,"width":815,"height":805,"rx":61,"fill":108,"stroke":108,"style":109},[97,898,882],{"x":818,"y":893,"fill":86,"style":120,"textAnchor":115},[57,900],{"x":822,"y":890,"width":815,"height":805,"rx":61,"fill":94,"stroke":86,"style":831},[97,902,903],{"x":825,"y":893,"fill":86,"style":120,"textAnchor":115},"No rendering to block",[191,905,907],{"id":906},"verification","Verification",[15,909,910,911,913],{},"After removing TLA from the critical path, the trace should show the first render starting without waiting for the config request, and the request overlapping rendering. Bundler output should no longer contain async module wrappers for the entry graph. For libraries, confirm ",[30,912,43],{}," works in supported Node versions.",[191,915,917],{"id":916},"worked-example-feature-flags-blocking-lcp","Worked Example: Feature Flags Blocking LCP",[15,919,920,921,924],{},"A storefront loaded feature flags in a module with ",[30,922,923],{},"export const flags = await fetchFlags()",", imported by the product page component. LCP on mobile included a 380ms flags request that started only after the entry chunk evaluated. The team replaced it with server-rendered flags inlined into the HTML for the initial page and a client-side refresh on navigation. The flags request vanished from the critical path; LCP p75 improved by roughly 300ms on client-rendered routes and the product page's first interaction no longer waited on flag evaluation.",[191,926,928],{"id":927},"common-mistakes","Common Mistakes",[196,930,931,937,943,953],{},[199,932,933,936],{},[202,934,935],{},"TLA in shared utility modules."," Their reach is unpredictable; any importer anywhere becomes async.",[199,938,939,942],{},[202,940,941],{},"Using TLA to \"simplify\" initialisation in libraries."," It breaks CommonJS consumers and forces every consumer's graph to wait.",[199,944,945,948,949,952],{},[202,946,947],{},"Awaiting several independent requests sequentially at top level."," Even where TLA is acceptable, use ",[30,950,951],{},"Promise.all"," for independent work.",[199,954,955,958],{},[202,956,957],{},"Assuming bundling removes the cost."," Bundlers preserve TLA semantics; the wait remains.",[191,960,962],{"id":961},"edge-cases","Edge Cases",[15,964,965,968],{},[202,966,967],{},"Circular imports with TLA."," Cycles involving async modules can deadlock or produce surprising evaluation orders. Avoid TLA anywhere near cycles.",[15,970,971,974],{},[202,972,973],{},"Server-side rendering."," In SSR, TLA in a shared module delays server rendering of every request that loads the module for the first time, and can stall cold starts in serverless functions.",[15,976,977,980],{},[202,978,979],{},"Service workers."," Module service workers do not allow top-level await in many engines — a TLA module imported by a service worker can fail to install.",[15,982,983,986],{},[202,984,985],{},"Testing environments."," Test runners that transform code to CommonJS may fail on TLA; prefer explicit initialisers for code under test.",[191,988,990],{"id":989},"faq","FAQ",[992,993,996,1000],"details",{"className":994},[995],"faq-item",[997,998,999],"summary",{},"Does top-level await block the browser's main thread?",[15,1001,1002],{},"No — the main thread is free while the awaited promise is pending, so input and painting can happen. What is blocked is the evaluation of the module graph, so your application's code (and its rendering) does not run until the promise settles.",[992,1004,1006,1009],{"className":1005},[995],[997,1007,1008],{},"Do bundlers warn about top-level await?",[15,1010,1011],{},"Some do when targeting environments without TLA support, and Vite fails the build for targets that lack it. None warn about the performance implication of TLA on the critical path; that needs a lint rule or code review attention.",[992,1013,1015,1018],{"className":1014},[995],[997,1016,1017],{},"Is a dynamic import a better pattern?",[15,1019,1020,1021,1024],{},"Often. Moving the dependent feature behind ",[30,1022,1023],{},"import()"," keeps the main graph synchronous, and the lazy chunk can use TLA without affecting first render. That combination is how WebAssembly-backed features are commonly loaded.",[992,1026,1028,1031],{"className":1027},[995],[997,1029,1030],{},"Can I detect TLA in dependencies?",[15,1032,1033,1034,1036],{},"Check bundler warnings and output for async module wrappers, or scan published files for module-scope ",[30,1035,32],{},". Libraries that use TLA in entry points are rare but increasing; prefer alternatives when you find one on your critical path.",[992,1038,1040,1043],{"className":1039},[995],[997,1041,1042],{},"How do I find top-level await in a large codebase quickly?",[15,1044,1045,1046,1048,1049,1052,1053,1056],{},"Search for ",[30,1047,32],{}," that is not inside a function: most linters can flag it (ESLint's ",[30,1050,1051],{},"no-restricted-syntax"," with a selector for ",[30,1054,1055],{},"Program > VariableDeclaration AwaitExpression"," and similar patterns). Bundler output is another signal — Rollup and Vite wrap affected modules in async functions, and webpack marks them as async modules in stats.",[191,1058,1060],{"id":1059},"related","Related",[196,1062,1063,1070,1077],{},[199,1064,1065,1069],{},[19,1066,1068],{"href":1067},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002F","Shipping ESM-only packages"," — why library entry points should avoid TLA.",[199,1071,1072,1076],{},[19,1073,1075],{"href":1074},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Ffixing-waterfalls-from-nested-dynamic-imports\u002F","Fixing waterfalls from nested dynamic imports"," — another way loading becomes serial.",[199,1078,1079,1083],{},[19,1080,1082],{"href":1081},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002Fdiagnosing-lcp-resource-load-delay\u002F","Diagnosing LCP resource load delay"," — how startup blocking shows up in LCP phases.",[682,1085,1087],{"type":1086},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Top-Level Await and Its Hidden Module Loading Cost\",\n  \"description\": \"How top-level await changes module evaluation order and timing, when it delays your first render, and safer patterns for asynchronous initialisation.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Replace TLA with explicit async initialisation\",\n      \"text\": \"\u002F\u002F After — config.js let configPromise; export function getConfig() { configPromise ??= fetch('\u002Fconfig.json').then((r) => r.json()); return configPromise; } \u002F\u002F trade-off: consumers must await getConfig() where they need it, which spreads \u002F\u002F async handling through the code.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Start critical requests early without blocking\",\n      \"text\": \"If the data is needed soon, kick off the request at startup but render the shell first.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Inline small configuration into the HTML\",\n      \"text\": \"For configuration known on the server, embed it as JSON in the page and read it synchronously.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Keep TLA for genuine module-level dependencies only\",\n      \"text\": \"TLA is reasonable for modules that cannot function without asynchronous setup and are loaded lazily anyway — a WebAssembly module behind a dynamic import, for example — where blocking only affects that lazy chunk.\"\n    }\n  ]\n}\n",[682,1089,1090],{"type":1086},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Top-Level Await and Its Hidden Module Loading Cost\",\n  \"description\": \"How top-level await changes module evaluation order and timing, when it delays your first render, and safer patterns for asynchronous initialisation.\",\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\u002Ftop-level-await-and-module-loading-cost\u002F\"\n  }\n}\n",[682,1092,1093],{"type":1086},"\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\": \"Top-Level Await and Module Loading Cost\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost\u002F\"\n    }\n  ]\n}\n",[1095,1096,1097],"style",{},"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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}",{"title":411,"searchDepth":424,"depth":424,"links":1099},[1100,1101,1102,1108,1109,1110,1111,1112,1113],{"id":193,"depth":424,"text":194},{"id":238,"depth":424,"text":239},{"id":398,"depth":424,"text":399,"children":1103},[1104,1105,1106,1107],{"id":403,"depth":484,"text":404},{"id":582,"depth":484,"text":583},{"id":662,"depth":484,"text":663},{"id":778,"depth":484,"text":779},{"id":906,"depth":424,"text":907},{"id":916,"depth":424,"text":917},{"id":927,"depth":424,"text":928},{"id":961,"depth":424,"text":962},{"id":989,"depth":424,"text":990},{"id":1059,"depth":424,"text":1060},"How top-level await changes module evaluation order and timing, when it delays your first render, and safer patterns for asynchronous initialisation.","md",{"slug":1117,"type":1118,"breadcrumb":1119,"datePublished":1127,"dateModified":1127},"top-level-await-and-module-loading-cost","article",[1120,1123,1124,1125],{"name":1121,"url":1122},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1126},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost\u002F","2026-10-06","\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost",{"title":13,"description":1130},"Top-level await pauses evaluation of a module and everything that imports it. Learn how it can delay rendering, serialise loading and break require(esm).","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Ftop-level-await-and-module-loading-cost\u002Findex","I3lWJJvsY122YQX4euguXj6DJsOT8aYKNHvgT-i3I4g",[1134,1138],{"title":1135,"path":1136,"stem":1137},"Shipping ESM-Only Packages","\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002Findex",{"title":1139,"path":1140,"stem":1141},"Using Import Maps for Unbundled Production","\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002Findex",1791308079863]