[{"data":1,"prerenderedAt":1076},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production":1067},{"id":4,"title":5,"body":6,"description":1046,"extension":1047,"meta":1048,"navigation":1060,"path":1061,"seo":1062,"stem":1065,"__hash__":1066},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002Findex.md","Using Import Maps for Unbundled Production",{"type":7,"value":8,"toc":1030},"minimark",[9,14,38,45,200,205,234,238,244,250,256,262,376,380,385,520,523,527,534,636,639,643,650,654,657,704,845,849,852,856,862,866,896,900,912,918,924,934,938,950,959,968,977,989,993,1016,1020,1023,1026],[10,11,13],"h1",{"id":12},"how-to-use-import-maps-for-unbundled-production","How to Use Import Maps for Unbundled Production",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide explores a no-bundler architecture 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",", part of ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". Browsers support ES modules natively, and import maps — a JSON block in the HTML that maps bare specifiers like ",[30,31,32],"code",{},"\"lit\""," to URLs — let source code use the same ",[30,35,36],{},"import { html } from 'lit'"," it would use with a bundler. Rails 7+ ships this approach by default, and many small and medium sites run in production without any JavaScript bundler.",[15,39,40,41,44],{},"The appeal is simplicity: no build step for JavaScript, each file cached independently, and deploys that change only the files you edited. The performance risk is the module waterfall: the browser discovers imports only as it parses each module, so a deep import graph loads level by level. With HTTP\u002F2 or HTTP\u002F3, ",[30,42,43],{},"modulepreload"," hints and a modest graph, unbundled sites can be fast; with a large graph and no preloading, they are slow.",[15,46,47],{},[48,49,55,56,55,63,55,67,55,70,55,79,55,85,55,90,55,99,55,106,55,110,55,114,55,117,55,121,55,127,55,131,55,134,55,138,55,143,55,150,55,156,55,160,55,164,55,168,55,172,55,176,55,180,55,184,55,188,55,192,55,196,55],"svg",{"viewBox":50,"width":51,"role":52,"ariaLabel":53,"style":54},"0 0 760 178","100%","img","Two timelines comparing a four-level unbundled ES module graph loaded with discovery waterfalls and with modulepreload hints for every module.","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",{},"Unbundled module graph: with and without modulepreload",[68,69,53],"desc",{},[57,71],{"x":72,"y":72,"width":73,"height":74,"rx":75,"fill":76,"stroke":77,"style":78},"1","758","176","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",[80,86,89],{"x":82,"y":87,"fill":77,"style":88},"74.0","font-size:12.5px;font-weight:700","No preload",[57,91],{"x":92,"y":93,"width":94,"height":95,"rx":96,"fill":97,"stroke":97,"style":98},"127.8","56.0","134.6","26.0","3","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[80,100,105],{"x":101,"y":102,"fill":77,"style":103,"textAnchor":104},"195.1","73.5","font-size:11.5px","middle","app.js",[57,107],{"x":108,"y":93,"width":94,"height":95,"rx":96,"fill":77,"stroke":77,"style":109},"263.9","fill-opacity:0.06;stroke-opacity:0.4",[80,111,113],{"x":112,"y":102,"fill":77,"style":103,"textAnchor":104},"331.3","router.js",[57,115],{"x":116,"y":93,"width":94,"height":95,"rx":96,"fill":77,"stroke":77,"style":109},"400.1",[80,118,120],{"x":119,"y":102,"fill":77,"style":103,"textAnchor":104},"467.4","lit",[57,122],{"x":123,"y":93,"width":94,"height":95,"rx":96,"fill":124,"stroke":125,"style":126},"536.2","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[80,128,130],{"x":129,"y":102,"fill":77,"style":103,"textAnchor":104},"603.5","components",[80,132,43],{"x":82,"y":133,"fill":77,"style":88},"112.0",[57,135],{"x":92,"y":136,"width":137,"height":95,"rx":96,"fill":124,"stroke":125,"style":126},"94.0","172.7",[80,139,142],{"x":140,"y":141,"fill":77,"style":103,"textAnchor":104},"214.2","111.5","all 14 modules in parallel",[144,145],"line",{"x1":146,"y1":147,"x2":148,"y2":147,"stroke":77,"style":149},"127.1","134.0","726.0","stroke-opacity:0.4",[80,151,155],{"x":152,"y":153,"fill":154,"style":103,"textAnchor":104},"137.8","151.0","#51617a","0ms",[80,157,159],{"x":158,"y":153,"fill":154,"style":103,"textAnchor":104},"181.5","100ms",[80,161,163],{"x":162,"y":153,"fill":154,"style":103,"textAnchor":104},"236.0","200ms",[80,165,167],{"x":166,"y":153,"fill":154,"style":103,"textAnchor":104},"290.4","300ms",[80,169,171],{"x":170,"y":153,"fill":154,"style":103,"textAnchor":104},"344.9","400ms",[80,173,175],{"x":174,"y":153,"fill":154,"style":103,"textAnchor":104},"399.3","500ms",[80,177,179],{"x":178,"y":153,"fill":154,"style":103,"textAnchor":104},"453.8","600ms",[80,181,183],{"x":182,"y":153,"fill":154,"style":103,"textAnchor":104},"508.2","700ms",[80,185,187],{"x":186,"y":153,"fill":154,"style":103,"textAnchor":104},"562.7","800ms",[80,189,191],{"x":190,"y":153,"fill":154,"style":103,"textAnchor":104},"617.1","900ms",[80,193,195],{"x":194,"y":153,"fill":154,"style":103,"textAnchor":104},"671.6","1000ms",[80,197,199],{"x":198,"y":153,"fill":154,"style":103,"textAnchor":104},"712.3","1100ms",[201,202,204],"h2",{"id":203},"rapid-diagnosis","Rapid Diagnosis",[206,207,208,216,222,228],"ul",{},[209,210,211,215],"li",{},[212,213,214],"strong",{},"Count modules on the critical path."," Network panel, filter JS. Dozens of small module requests at load suggest a graph that needs preloading or partial bundling.",[209,217,218,221],{},[212,219,220],{},"Look for staircase patterns"," in the waterfall: each level starting only after the previous finishes.",[209,223,224,227],{},[212,225,226],{},"Check protocol."," Unbundled loading over HTTP\u002F1.1 is impractical; confirm HTTP\u002F2 or HTTP\u002F3.",[209,229,230,233],{},[212,231,232],{},"Check compression and caching per file."," Small files compress less efficiently; long-lived caching of each file is the main benefit.",[201,235,237],{"id":236},"root-cause-analysis","Root Cause Analysis",[15,239,240,243],{},[212,241,242],{},"1. Import discovery waterfalls."," Each module's imports are only known after it is fetched and parsed.",[15,245,246,249],{},[212,247,248],{},"2. Per-request overhead."," Hundreds of small files add request headers, scheduling and less effective compression.",[15,251,252,255],{},[212,253,254],{},"3. No tree shaking."," Without a bundler, unused exports in imported files still download; only file-level granularity applies.",[15,257,258,261],{},[212,259,260],{},"4. Third-party packages not designed for direct use."," Many npm packages ship CommonJS or deep internal import graphs unsuitable for the browser without bundling.",[15,263,264],{},[48,265,55,268,55,271,55,274,55,276,55,279,55,281,55,285,55,291,55,295,55,299,55,302,55,306,55,310,55,314,55,316,55,320,55,322,55,325,55,328,55,332,55,334,55,337,55,339,55,342,55,345,55,349,55,351,55,354,55,356,55,359,55,362,55,366,55,368,55,371,55,373,55],{"viewBox":266,"width":51,"role":52,"ariaLabel":267,"style":54},"0 0 760 228","Factors that make unbundled production delivery with import maps suitable or unsuitable.",[57,269],{"className":270,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,272,273],{},"When unbundled with import maps is a good fit",[68,275,267],{},[57,277],{"x":72,"y":72,"width":73,"height":278,"rx":75,"fill":76,"stroke":77,"style":78},"226",[80,280,273],{"x":82,"y":83,"fill":77,"style":84},[57,282],{"x":82,"y":93,"width":283,"height":284,"rx":61,"fill":77,"stroke":77,"style":109},"157.6","30.0",[80,286,290],{"x":287,"y":288,"fill":77,"style":88,"textAnchor":289},"38.0","75.5","start","Factor",[57,292],{"x":293,"y":93,"width":294,"height":284,"rx":61,"fill":77,"stroke":77,"style":109},"185.6","273.2",[80,296,298],{"x":297,"y":288,"fill":77,"style":88,"textAnchor":104},"322.2","Good fit",[57,300],{"x":301,"y":93,"width":294,"height":284,"rx":61,"fill":77,"stroke":77,"style":109},"458.8",[80,303,305],{"x":304,"y":288,"fill":77,"style":88,"textAnchor":104},"595.4","Poor fit",[57,307],{"x":82,"y":308,"width":283,"height":284,"rx":61,"fill":76,"stroke":77,"style":309},"86.0","stroke-opacity:0.35",[80,311,313],{"x":287,"y":312,"fill":77,"style":88,"textAnchor":289},"105.5","Modules on critical path",[57,315],{"x":293,"y":308,"width":294,"height":284,"rx":61,"fill":97,"stroke":97,"style":98},[80,317,319],{"x":297,"y":312,"fill":77,"style":318,"textAnchor":104},"font-size:12px","tens",[57,321],{"x":301,"y":308,"width":294,"height":284,"rx":61,"fill":124,"stroke":125,"style":126},[80,323,324],{"x":304,"y":312,"fill":77,"style":318,"textAnchor":104},"hundreds",[57,326],{"x":82,"y":327,"width":283,"height":284,"rx":61,"fill":76,"stroke":77,"style":309},"116.0",[80,329,331],{"x":287,"y":330,"fill":77,"style":88,"textAnchor":289},"135.5","Dependencies",[57,333],{"x":293,"y":327,"width":294,"height":284,"rx":61,"fill":97,"stroke":97,"style":98},[80,335,336],{"x":297,"y":330,"fill":77,"style":318,"textAnchor":104},"ESM-native, few",[57,338],{"x":301,"y":327,"width":294,"height":284,"rx":61,"fill":124,"stroke":125,"style":126},[80,340,341],{"x":304,"y":330,"fill":77,"style":318,"textAnchor":104},"many CJS packages",[57,343],{"x":82,"y":344,"width":283,"height":284,"rx":61,"fill":76,"stroke":77,"style":309},"146.0",[80,346,348],{"x":287,"y":347,"fill":77,"style":88,"textAnchor":289},"165.5","Interactivity",[57,350],{"x":293,"y":344,"width":294,"height":284,"rx":61,"fill":97,"stroke":97,"style":98},[80,352,353],{"x":297,"y":347,"fill":77,"style":318,"textAnchor":104},"progressive enhancement",[57,355],{"x":301,"y":344,"width":294,"height":284,"rx":61,"fill":124,"stroke":125,"style":126},[80,357,358],{"x":304,"y":347,"fill":77,"style":318,"textAnchor":104},"large SPA",[57,360],{"x":82,"y":361,"width":283,"height":284,"rx":61,"fill":76,"stroke":77,"style":309},"176.0",[80,363,365],{"x":287,"y":364,"fill":77,"style":88,"textAnchor":289},"195.5","Deploy cadence",[57,367],{"x":293,"y":361,"width":294,"height":284,"rx":61,"fill":97,"stroke":97,"style":98},[80,369,370],{"x":297,"y":364,"fill":77,"style":318,"textAnchor":104},"frequent, small edits",[57,372],{"x":301,"y":361,"width":294,"height":284,"rx":61,"fill":77,"stroke":77,"style":109},[80,374,375],{"x":304,"y":364,"fill":77,"style":318,"textAnchor":104},"any",[201,377,379],{"id":378},"step-by-step-resolution","Step-by-Step Resolution",[381,382,384],"h3",{"id":383},"_1-define-the-import-map-and-pin-versions","1. Define the import map and pin versions",[386,387,392],"pre",{"className":388,"code":389,"language":390,"meta":391,"style":391},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Cscript type=\"importmap\">\n{\n  \"imports\": {\n    \"lit\": \"\u002Fvendor\u002Flit@3.2.1\u002Findex.js\",\n    \"lit\u002F\": \"\u002Fvendor\u002Flit@3.2.1\u002F\",\n    \"@app\u002F\": \"\u002Fjs\u002F\"\n  }\n}\n\u003C\u002Fscript>\n\u003Cscript type=\"module\" src=\"\u002Fjs\u002Fapp-5c1e.js\">\u003C\u002Fscript>\n\u003C!-- trade-off: the import map must appear before any module script, and only\n     one map is allowed per document in older browsers. Generate it at build\n     or deploy time from a lock file so versions cannot drift. -->\n","html","",[30,393,394,420,426,432,438,444,450,456,462,472,501,508,514],{"__ignoreMap":391},[395,396,398,402,406,410,413,417],"span",{"class":144,"line":397},1,[395,399,401],{"class":400},"saISM","\u003C",[395,403,405],{"class":404},"sZBmE","script",[395,407,409],{"class":408},"sPXB4"," type",[395,411,412],{"class":400},"=",[395,414,416],{"class":415},"sZ8jY","\"importmap\"",[395,418,419],{"class":400},">\n",[395,421,423],{"class":144,"line":422},2,[395,424,425],{"class":400},"{\n",[395,427,429],{"class":144,"line":428},3,[395,430,431],{"class":400},"  \"imports\": {\n",[395,433,435],{"class":144,"line":434},4,[395,436,437],{"class":400},"    \"lit\": \"\u002Fvendor\u002Flit@3.2.1\u002Findex.js\",\n",[395,439,441],{"class":144,"line":440},5,[395,442,443],{"class":400},"    \"lit\u002F\": \"\u002Fvendor\u002Flit@3.2.1\u002F\",\n",[395,445,447],{"class":144,"line":446},6,[395,448,449],{"class":400},"    \"@app\u002F\": \"\u002Fjs\u002F\"\n",[395,451,453],{"class":144,"line":452},7,[395,454,455],{"class":400},"  }\n",[395,457,459],{"class":144,"line":458},8,[395,460,461],{"class":400},"}\n",[395,463,465,468,470],{"class":144,"line":464},9,[395,466,467],{"class":400},"\u003C\u002F",[395,469,405],{"class":404},[395,471,419],{"class":400},[395,473,475,477,479,481,483,486,489,491,494,497,499],{"class":144,"line":474},10,[395,476,401],{"class":400},[395,478,405],{"class":404},[395,480,409],{"class":408},[395,482,412],{"class":400},[395,484,485],{"class":415},"\"module\"",[395,487,488],{"class":408}," src",[395,490,412],{"class":400},[395,492,493],{"class":415},"\"\u002Fjs\u002Fapp-5c1e.js\"",[395,495,496],{"class":400},">\u003C\u002F",[395,498,405],{"class":404},[395,500,419],{"class":400},[395,502,504],{"class":144,"line":503},11,[395,505,507],{"class":506},"sjfSM","\u003C!-- trade-off: the import map must appear before any module script, and only\n",[395,509,511],{"class":144,"line":510},12,[395,512,513],{"class":506},"     one map is allowed per document in older browsers. Generate it at build\n",[395,515,517],{"class":144,"line":516},13,[395,518,519],{"class":506},"     or deploy time from a lock file so versions cannot drift. -->\n",[15,521,522],{},"Expected outcome: source uses bare specifiers; the browser resolves them through the map.",[381,524,526],{"id":525},"_2-preload-the-critical-module-graph","2. Preload the critical module graph",[15,528,529,530,533],{},"Emit ",[30,531,532],{},"\u003Clink rel=\"modulepreload\">"," for every module reached at startup, so the browser fetches them in parallel instead of discovering them level by level.",[386,535,537],{"className":388,"code":536,"language":390,"meta":391,"style":391},"\u003Clink rel=\"modulepreload\" href=\"\u002Fjs\u002Fapp-5c1e.js\">\n\u003Clink rel=\"modulepreload\" href=\"\u002Fjs\u002Frouter-0a7d.js\">\n\u003Clink rel=\"modulepreload\" href=\"\u002Fvendor\u002Flit@3.2.1\u002Findex.js\">\n\u003Clink rel=\"modulepreload\" href=\"\u002Fvendor\u002Flit@3.2.1\u002Flit-element.js\">\n\u003C!-- trade-off: hand-maintained preload lists rot. Generate them by walking the\n     import graph at deploy time (tools like importmap-rails or JSPM do this). -->\n",[30,538,539,563,584,605,626,631],{"__ignoreMap":391},[395,540,541,543,546,549,551,554,557,559,561],{"class":144,"line":397},[395,542,401],{"class":400},[395,544,545],{"class":404},"link",[395,547,548],{"class":408}," rel",[395,550,412],{"class":400},[395,552,553],{"class":415},"\"modulepreload\"",[395,555,556],{"class":408}," href",[395,558,412],{"class":400},[395,560,493],{"class":415},[395,562,419],{"class":400},[395,564,565,567,569,571,573,575,577,579,582],{"class":144,"line":422},[395,566,401],{"class":400},[395,568,545],{"class":404},[395,570,548],{"class":408},[395,572,412],{"class":400},[395,574,553],{"class":415},[395,576,556],{"class":408},[395,578,412],{"class":400},[395,580,581],{"class":415},"\"\u002Fjs\u002Frouter-0a7d.js\"",[395,583,419],{"class":400},[395,585,586,588,590,592,594,596,598,600,603],{"class":144,"line":428},[395,587,401],{"class":400},[395,589,545],{"class":404},[395,591,548],{"class":408},[395,593,412],{"class":400},[395,595,553],{"class":415},[395,597,556],{"class":408},[395,599,412],{"class":400},[395,601,602],{"class":415},"\"\u002Fvendor\u002Flit@3.2.1\u002Findex.js\"",[395,604,419],{"class":400},[395,606,607,609,611,613,615,617,619,621,624],{"class":144,"line":434},[395,608,401],{"class":400},[395,610,545],{"class":404},[395,612,548],{"class":408},[395,614,412],{"class":400},[395,616,553],{"class":415},[395,618,556],{"class":408},[395,620,412],{"class":400},[395,622,623],{"class":415},"\"\u002Fvendor\u002Flit@3.2.1\u002Flit-element.js\"",[395,625,419],{"class":400},[395,627,628],{"class":144,"line":440},[395,629,630],{"class":506},"\u003C!-- trade-off: hand-maintained preload lists rot. Generate them by walking the\n",[395,632,633],{"class":144,"line":446},[395,634,635],{"class":506},"     import graph at deploy time (tools like importmap-rails or JSPM do this). -->\n",[15,637,638],{},"Expected outcome: the waterfall collapses into one parallel batch.",[381,640,642],{"id":641},"_3-fingerprint-files-and-cache-immutably","3. Fingerprint files and cache immutably",[15,644,645,646,649],{},"Include content hashes in file names (or in the import map's URLs) and serve them with ",[30,647,648],{},"Cache-Control: public, max-age=31536000, immutable",". Deploys change only the hashes of edited files; everything else stays cached — a genuine advantage over bundles where one change rehashes a large chunk.",[381,651,653],{"id":652},"_4-bundle-selectively-where-it-pays","4. Bundle selectively where it pays",[15,655,656],{},"Pre-bundle heavy dependencies with deep graphs into a single ESM file each (with esbuild or a CDN's bundled ESM endpoint), and keep your own code unbundled. This hybrid keeps most of the simplicity while removing the worst waterfalls.",[386,658,662],{"className":659,"code":660,"language":661,"meta":391,"style":391},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","npx esbuild node_modules\u002Fsome-chart-lib\u002Findex.js --bundle --format=esm --minify \\\n  --outfile=public\u002Fvendor\u002Fsome-chart-lib.js\n# trade-off: pre-bundled vendor files lose cross-package deduplication; if two\n# pre-bundled libraries share a dependency, it is included twice.\n","bash",[30,663,664,689,694,699],{"__ignoreMap":391},[395,665,666,670,673,676,679,682,685],{"class":144,"line":397},[395,667,669],{"class":668},"sQw3B","npx",[395,671,672],{"class":415}," esbuild",[395,674,675],{"class":415}," node_modules\u002Fsome-chart-lib\u002Findex.js",[395,677,678],{"class":408}," --bundle",[395,680,681],{"class":408}," --format=esm",[395,683,684],{"class":408}," --minify",[395,686,688],{"class":687},"sPARh"," \\\n",[395,690,691],{"class":144,"line":422},[395,692,693],{"class":408},"  --outfile=public\u002Fvendor\u002Fsome-chart-lib.js\n",[395,695,696],{"class":144,"line":428},[395,697,698],{"class":506},"# trade-off: pre-bundled vendor files lose cross-package deduplication; if two\n",[395,700,701],{"class":144,"line":434},[395,702,703],{"class":506},"# pre-bundled libraries share a dependency, it is included twice.\n",[15,705,706],{},[48,707,55,710,55,713,55,716,55,718,55,735,55,738,55,740,55,745,55,751,55,755,55,758,55,764,55,772,55,778,55,782,55,787,55,791,55,795,55,797,55,800,55,803,55,806,55,810,55,813,55,816,55,820,55,822,55,825,55,828,55,831,55,835,55,838,55,841,55],{"viewBox":708,"width":51,"role":52,"ariaLabel":709,"style":54},"0 0 760 342","Decision sequence for choosing between unbundled import maps, a hybrid approach and a full bundler.",[57,711],{"className":712,"x":61,"y":61,"width":51,"height":51,"fill":62},[60],[64,714,715],{},"Unbundled, hybrid or bundled?",[68,717,709],{},[719,720,721],"defs",{},[722,723,730],"marker",{"id":724,"viewBox":725,"refX":726,"refY":727,"markerWidth":728,"markerHeight":728,"orient":729},"fa8cfc1893","0 0 10 10","9","5","7","auto-start-reverse",[731,732],"path",{"d":733,"fill":77,"style":734},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[57,736],{"x":72,"y":72,"width":73,"height":737,"rx":75,"fill":76,"stroke":77,"style":78},"340",[80,739,715],{"x":82,"y":83,"fill":77,"style":84},[57,741],{"x":82,"y":93,"width":742,"height":743,"rx":744,"fill":77,"stroke":77,"style":109},"320.0","52.0","6",[80,746,750],{"x":747,"y":748,"fill":77,"style":749,"textAnchor":104},"188.0","78.0","font-size:13px;font-weight:700","Under ~50 modules at startup, ESM",[80,752,754],{"x":747,"y":753,"fill":77,"style":749,"textAnchor":104},"95.0","dependencies?",[57,756],{"x":757,"y":93,"width":742,"height":743,"rx":744,"fill":97,"stroke":97,"style":98},"412.0",[80,759,763],{"x":760,"y":761,"fill":77,"style":762,"textAnchor":104},"572.0","86.5","font-size:12.5px","Unbundled with import map + modulepreload",[144,765],{"x1":766,"y1":767,"x2":768,"y2":767,"stroke":77,"strokeWidth":769,"style":770,"markerEnd":771},"348.0","82.0","410.0","1.5","stroke-opacity:0.6","url(#fa8cfc1893)",[80,773,777],{"x":774,"y":775,"fill":97,"style":776,"textAnchor":104},"380.0","75.0","font-size:11.5px;font-weight:700","yes",[144,779],{"x1":747,"y1":780,"x2":747,"y2":781,"stroke":77,"strokeWidth":769,"style":770,"markerEnd":771},"108.0","142.0",[80,783,786],{"x":784,"y":785,"fill":154,"style":776},"196.0","130.0","no",[57,788],{"x":82,"y":789,"width":742,"height":790,"rx":744,"fill":77,"stroke":77,"style":109},"144.0","35.0",[80,792,794],{"x":747,"y":793,"fill":77,"style":749,"textAnchor":104},"166.0","A few heavy dependencies with deep graphs?",[57,796],{"x":757,"y":789,"width":742,"height":790,"rx":744,"fill":97,"stroke":97,"style":98},[80,798,799],{"x":760,"y":793,"fill":77,"style":762,"textAnchor":104},"Hybrid — pre-bundle those, keep app unbundled",[144,801],{"x1":766,"y1":802,"x2":768,"y2":802,"stroke":77,"strokeWidth":769,"style":770,"markerEnd":771},"161.5",[80,804,777],{"x":774,"y":805,"fill":97,"style":776,"textAnchor":104},"154.5",[144,807],{"x1":747,"y1":808,"x2":747,"y2":809,"stroke":77,"strokeWidth":769,"style":770,"markerEnd":771},"179.0","213.0",[80,811,786],{"x":784,"y":812,"fill":154,"style":776},"201.0",[57,814],{"x":82,"y":815,"width":742,"height":790,"rx":744,"fill":77,"stroke":77,"style":109},"215.0",[80,817,819],{"x":747,"y":818,"fill":77,"style":749,"textAnchor":104},"237.0","Large SPA with hundreds of modules?",[57,821],{"x":757,"y":815,"width":742,"height":790,"rx":744,"fill":124,"stroke":125,"style":126},[80,823,824],{"x":760,"y":818,"fill":77,"style":762,"textAnchor":104},"Use a bundler with route splitting",[144,826],{"x1":766,"y1":827,"x2":768,"y2":827,"stroke":77,"strokeWidth":769,"style":770,"markerEnd":771},"232.5",[80,829,777],{"x":774,"y":830,"fill":97,"style":776,"textAnchor":104},"225.5",[144,832],{"x1":747,"y1":833,"x2":747,"y2":834,"stroke":77,"strokeWidth":769,"style":770,"markerEnd":771},"250.0","284.0",[80,836,786],{"x":784,"y":837,"fill":154,"style":776},"272.0",[57,839],{"x":82,"y":840,"width":742,"height":83,"rx":744,"fill":124,"stroke":125,"style":126},"286.0",[80,842,844],{"x":747,"y":843,"fill":77,"style":762,"textAnchor":104},"307.5","Start hybrid and measure the waterfall",[201,846,848],{"id":847},"verification","Verification",[15,850,851],{},"Record a throttled trace: all startup modules should be requested in a single parallel batch shortly after the HTML, with no staircase. Compare LCP and TBT with a bundled build of the same app if available. After a small deploy, confirm returning visitors re-download only the changed files (transfer sizes of zero for cached modules in the Network panel).",[201,853,855],{"id":854},"worked-example-a-content-site-with-progressive-enhancement","Worked Example: A Content Site with Progressive Enhancement",[15,857,858,859,861],{},"A documentation site used about 25 small modules for search, theme toggling and code-copy buttons, plus one web-component library. Initially unbundled without preloads, its startup JavaScript loaded in four sequential levels, finishing at 1.1s on a mobile profile. Adding generated ",[30,860,43],{}," links collapsed it to one batch finishing at 330ms; pre-bundling the web-component library removed eleven tiny requests. The team kept the setup because deploys — mostly content and small script tweaks — now invalidated one or two files instead of a whole bundle, and the build pipeline lost its JavaScript step entirely.",[201,863,865],{"id":864},"common-mistakes","Common Mistakes",[206,867,868,874,884,890],{},[209,869,870,873],{},[212,871,872],{},"Shipping unbundled code without preloads."," The waterfall is the defining performance risk of this approach.",[209,875,876,879,880,883],{},[212,877,878],{},"Pointing the import map at third-party CDNs without integrity."," Use Subresource Integrity (the ",[30,881,882],{},"integrity"," map in import maps where supported) or self-host.",[209,885,886,889],{},[212,887,888],{},"Unminified source in production."," No bundler does not mean no minifier; minify files individually.",[209,891,892,895],{},[212,893,894],{},"Ignoring old browsers."," Import maps are widely supported now; if you must support older engines, a shim exists, but it adds startup cost.",[201,897,899],{"id":898},"edge-cases","Edge Cases",[15,901,902,55,905,908,909,911],{},[212,903,904],{},"Dynamic imports and the map.",[30,906,907],{},"import('lit\u002Fdirectives\u002Frepeat.js')"," resolves through the map like static imports, so lazy loading works — but lazy modules are not covered by startup ",[30,910,43],{}," and may create their own small waterfalls when triggered.",[15,913,914,917],{},[212,915,916],{},"Multiple import maps."," Modern browsers allow multiple import maps, merged in order, which helps micro-frontends; older versions accepted only one. Test in your oldest supported browser.",[15,919,920,923],{},[212,921,922],{},"Service workers."," A service worker can precache the whole module graph for repeat visits, removing network cost entirely for returning users — a natural fit for an unbundled site with many small files.",[15,925,926,929,930,933],{},[212,927,928],{},"CSS in modules."," CSS module scripts (",[30,931,932],{},"import sheet from '.\u002Fx.css' with { type: 'css' }",") are an option in some engines; for broad support, keep CSS as normal stylesheets.",[201,935,937],{"id":936},"faq","FAQ",[939,940,943,947],"details",{"className":941},[942],"faq-item",[944,945,946],"summary",{},"Is HTTP\u002F2 enough to make unbundled loading fast?",[15,948,949],{},"HTTP\u002F2 removes the per-connection request limit and makes many small requests cheap, but it cannot fix discovery waterfalls — the browser still does not know about a module until its importer is parsed. Preloading is what fixes that.",[939,951,953,956],{"className":952},[942],[944,954,955],{},"Does an import map block rendering?",[15,957,958],{},"It is an inline script element that the parser processes quickly; it does not fetch anything itself. The cost is that module scripts cannot start resolving bare specifiers until the map is parsed, which is why it belongs early in the head.",[939,960,962,965],{"className":961},[942],[944,963,964],{},"Can I use npm packages directly?",[15,966,967],{},"Only those published as browser-ready ESM without Node-specific imports. Many are; many are not. Tools like JSPM or ESM CDNs can generate browser-ready versions and maps for you.",[939,969,971,974],{"className":970},[942],[944,972,973],{},"How does this compare with Vite in development?",[15,975,976],{},"Vite's dev server also serves unbundled ES modules, but it pre-bundles dependencies and transforms source on demand. Production with import maps is the same idea without the dev-server conveniences — closer to what the browser runs in development with Vite than a bundled production build is.",[939,978,980,983],{"className":979},[942],[944,981,982],{},"What about integrity for modules loaded through import maps?",[15,984,985,986,988],{},"Import maps support an ",[30,987,882],{}," field mapping module URLs to Subresource Integrity hashes in browsers that implement it, so dynamically imported and transitively loaded modules can be verified. Where unsupported, self-hosting the modules on your own origin is the safer default.",[201,990,992],{"id":991},"related","Related",[206,994,995,1002,1009],{},[209,996,997,1001],{},[19,998,1000],{"href":999},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fresource-hints-and-early-hints\u002Fusing-modulepreload-for-es-module-graphs\u002F","Using modulepreload for ES module graphs"," — the hint that makes this approach viable.",[209,1003,1004,1008],{},[19,1005,1007],{"href":1006},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fshipping-esm-only-packages\u002F","Shipping ESM-only packages"," — dependencies that work without a bundler.",[209,1010,1011,1015],{},[19,1012,1014],{"href":1013},"\u002Fnetwork-protocol-optimization\u002Fhttp2-http3-and-connection-management\u002Fmeasuring-real-world-http3-gains\u002F","Measuring real-world HTTP\u002F3 gains"," — the transport many-file delivery depends on.",[405,1017,1019],{"type":1018},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Use Import Maps for Unbundled Production\",\n  \"description\": \"How import maps let browsers resolve bare module specifiers without a bundler, and what it takes to make an unbundled production site load quickly.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Define the import map and pin versions\",\n      \"text\": \"Expected outcome: source uses bare specifiers; the browser resolves them through the map.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Preload the critical module graph\",\n      \"text\": \"Emit for every module reached at startup, so the browser fetches them in parallel instead of discovering them level by level.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Fingerprint files and cache immutably\",\n      \"text\": \"Include content hashes in file names (or in the import map's URLs) and serve them with Cache-Control: public, max-age=31536000, immutable.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Bundle selectively where it pays\",\n      \"text\": \"Pre-bundle heavy dependencies with deep graphs into a single ESM file each (with esbuild or a CDN's bundled ESM endpoint), and keep your own code unbundled.\"\n    }\n  ]\n}\n",[405,1021,1022],{"type":1018},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Use Import Maps for Unbundled Production\",\n  \"description\": \"How import maps let browsers resolve bare module specifiers without a bundler, and what it takes to make an unbundled production site load quickly.\",\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\u002Fusing-import-maps-for-unbundled-production\u002F\"\n  }\n}\n",[405,1024,1025],{"type":1018},"\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\": \"Using Import Maps for Unbundled Production\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002F\"\n    }\n  ]\n}\n",[1027,1028,1029],"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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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 .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}",{"title":391,"searchDepth":422,"depth":422,"links":1031},[1032,1033,1034,1040,1041,1042,1043,1044,1045],{"id":203,"depth":422,"text":204},{"id":236,"depth":422,"text":237},{"id":378,"depth":422,"text":379,"children":1035},[1036,1037,1038,1039],{"id":383,"depth":428,"text":384},{"id":525,"depth":428,"text":526},{"id":641,"depth":428,"text":642},{"id":652,"depth":428,"text":653},{"id":847,"depth":422,"text":848},{"id":854,"depth":422,"text":855},{"id":864,"depth":422,"text":865},{"id":898,"depth":422,"text":899},{"id":936,"depth":422,"text":937},{"id":991,"depth":422,"text":992},"How import maps let browsers resolve bare module specifiers without a bundler, and what it takes to make an unbundled production site load quickly.","md",{"slug":1049,"type":1050,"breadcrumb":1051,"datePublished":1059,"dateModified":1059},"using-import-maps-for-unbundled-production","article",[1052,1055,1056,1057],{"name":1053,"url":1054},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1058},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002F","2026-10-06",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production",{"title":1063,"description":1064},"Using Import Maps for Unbundled Production Sites","Ship native ES modules with import maps instead of a bundle: when it is fast enough, how to avoid waterfalls with modulepreload, and how caching changes.","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Fusing-import-maps-for-unbundled-production\u002Findex","UfgAWawiJgVNUl74KhCqeZpHAkUapDTDpmHXWusJWBw",[1068,1072],{"title":1069,"path":1070,"stem":1071},"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",{"title":1073,"path":1074,"stem":1075},"Polyfills & Differential Serving","\u002Fjavascript-bundle-optimization-code-splitting\u002Fpolyfills-and-differential-serving","javascript-bundle-optimization-code-splitting\u002Fpolyfills-and-differential-serving\u002Findex",1791308077858]