CINXE.COM
<!doctype html><html lang="en"><head><title data-rh="true">Adopting Bazel for Web at Scale. How and Why We Migrated Airbnb’s… | by Sharmila Jesupaul | The Airbnb Tech Blog | Nov, 2024 | Medium</title><meta data-rh="true" charset="utf-8"/><meta data-rh="true" name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,maximum-scale=1"/><meta data-rh="true" name="theme-color" content="#000000"/><meta data-rh="true" name="twitter:app:name:iphone" content="Medium"/><meta data-rh="true" name="twitter:app:id:iphone" content="828256236"/><meta data-rh="true" property="al:ios:app_name" content="Medium"/><meta data-rh="true" property="al:ios:app_store_id" content="828256236"/><meta data-rh="true" property="al:android:package" content="com.medium.reader"/><meta data-rh="true" property="fb:app_id" content="542599432471018"/><meta data-rh="true" property="og:site_name" content="Medium"/><meta data-rh="true" property="og:type" content="article"/><meta data-rh="true" property="article:published_time" content="2024-11-12T21:49:47.261Z"/><meta data-rh="true" name="title" content="Adopting Bazel for Web at Scale. How and Why We Migrated Airbnb’s… | by Sharmila Jesupaul | The Airbnb Tech Blog | Nov, 2024 | Medium"/><meta data-rh="true" property="og:title" content="Adopting Bazel for Web at Scale"/><meta data-rh="true" property="al:android:url" content="medium://p/a784b2dbe325"/><meta data-rh="true" property="al:ios:url" content="medium://p/a784b2dbe325"/><meta data-rh="true" property="al:android:app_name" content="Medium"/><meta data-rh="true" name="description" content="At Airbnb, we’ve recently adopted Bazel — Google’s open source build tool–as our universal build system across backend, web, and iOS platforms. This post will cover our experience adopting Bazel for…"/><meta data-rh="true" property="og:description" content="How and Why We Migrated Airbnb’s Large-Scale Web Monorepo to Bazel"/><meta data-rh="true" property="og:url" content="https://medium.com/airbnb-engineering/adopting-bazel-for-web-at-scale-a784b2dbe325"/><meta data-rh="true" property="al:web:url" content="https://medium.com/airbnb-engineering/adopting-bazel-for-web-at-scale-a784b2dbe325"/><meta data-rh="true" property="og:image" content="https://miro.medium.com/v2/resize:fit:1200/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg"/><meta data-rh="true" property="og:image:alt" content="A person making pesto sauce with a mortar and pestle"/><meta data-rh="true" property="article:author" content="https://medium.com/@sharmila.jesupaul"/><meta data-rh="true" name="author" content="Sharmila Jesupaul"/><meta data-rh="true" name="robots" content="index,noarchive,follow,max-image-preview:large"/><meta data-rh="true" name="referrer" content="unsafe-url"/><meta data-rh="true" property="twitter:title" content="Adopting Bazel for Web at Scale"/><meta data-rh="true" name="twitter:site" content="@AirbnbEng"/><meta data-rh="true" name="twitter:app:url:iphone" content="medium://p/a784b2dbe325"/><meta data-rh="true" property="twitter:description" content="How and Why We Migrated Airbnb’s Large-Scale Web Monorepo to Bazel"/><meta data-rh="true" name="twitter:image:src" content="https://miro.medium.com/v2/resize:fit:1200/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg"/><meta data-rh="true" name="twitter:image:alt" content="A person making pesto sauce with a mortar and pestle"/><meta data-rh="true" name="twitter:card" content="summary_large_image"/><meta data-rh="true" name="twitter:label1" content="Reading time"/><meta data-rh="true" name="twitter:data1" content="10 min read"/><link data-rh="true" rel="icon" href="https://miro.medium.com/v2/5d8de952517e8160e40ef9841c781cdc14a5db313057fa3c3de41c6f5b494b19"/><link data-rh="true" rel="search" type="application/opensearchdescription+xml" title="Medium" href="/osd.xml"/><link data-rh="true" rel="apple-touch-icon" sizes="152x152" href="https://miro.medium.com/v2/resize:fill:304:304/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="120x120" href="https://miro.medium.com/v2/resize:fill:240:240/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="76x76" href="https://miro.medium.com/v2/resize:fill:152:152/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="60x60" href="https://miro.medium.com/v2/resize:fill:120:120/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="mask-icon" href="https://miro.medium.com/v2/resize:fill:1000:1000/7*GAOKVe--MXbEJmV9230oOQ.png" color="#171717"/><link data-rh="true" rel="preconnect" href="https://glyph.medium.com" crossOrigin=""/><link data-rh="true" id="glyph_preload_link" rel="preload" as="style" type="text/css" href="https://glyph.medium.com/css/unbound.css"/><link data-rh="true" id="glyph_link" rel="stylesheet" type="text/css" href="https://glyph.medium.com/css/unbound.css"/><link data-rh="true" rel="author" href="https://medium.com/@sharmila.jesupaul"/><link data-rh="true" rel="canonical" href="https://medium.com/airbnb-engineering/adopting-bazel-for-web-at-scale-a784b2dbe325"/><link data-rh="true" rel="alternate" href="android-app://com.medium.reader/https/medium.com/p/a784b2dbe325"/><script data-rh="true" type="application/ld+json">{"@context":"http:\u002F\u002Fschema.org","@type":"NewsArticle","image":["https:\u002F\u002Fmiro.medium.com\u002Fv2\u002Fresize:fit:1200\u002F1*uMA-yyBcSyRjQBwdQnbDdw.jpeg"],"url":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Fadopting-bazel-for-web-at-scale-a784b2dbe325","dateCreated":"2024-11-12T18:22:17.235Z","datePublished":"2024-11-12T18:22:17.235Z","dateModified":"2024-11-15T07:17:21.051Z","headline":"Adopting Bazel for Web at Scale - The Airbnb Tech Blog - Medium","name":"Adopting Bazel for Web at Scale - The Airbnb Tech Blog - Medium","description":"At Airbnb, we’ve recently adopted Bazel — Google’s open source build tool–as our universal build system across backend, web, and iOS platforms. This post will cover our experience adopting Bazel for…","identifier":"a784b2dbe325","author":{"@type":"Person","name":"Sharmila Jesupaul","url":"https:\u002F\u002Fmedium.com\u002F@sharmila.jesupaul"},"creator":["Sharmila Jesupaul"],"publisher":{"@type":"Organization","name":"The Airbnb Tech Blog","url":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering","logo":{"@type":"ImageObject","width":60,"height":60,"url":"https:\u002F\u002Fmiro.medium.com\u002Fv2\u002Fresize:fit:120\u002F1*JZl-TXoSiG0VmYn3qWLdTA.png"}},"mainEntityOfPage":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Fadopting-bazel-for-web-at-scale-a784b2dbe325"}</script><style type="text/css" data-fela-rehydration="586" data-fela-type="STATIC">html{box-sizing:border-box;-webkit-text-size-adjust:100%}*, *:before, *:after{box-sizing:inherit}body{margin:0;padding:0;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;color:rgba(0,0,0,0.8);position:relative;min-height:100vh}h1, h2, h3, h4, h5, h6, dl, dd, ol, ul, menu, figure, blockquote, p, pre, form{margin:0}menu, ol, ul{padding:0;list-style:none;list-style-image:none}main{display:block}a{color:inherit;text-decoration:none}a, button, input{-webkit-tap-highlight-color:transparent}img, svg{vertical-align:middle}button{background:transparent;overflow:visible}button, input, optgroup, select, textarea{margin:0}:root{--reach-tabs:1;--reach-menu-button:1}#speechify-root{font-family:Sohne, sans-serif}div[data-popper-reference-hidden="true"]{visibility:hidden;pointer-events:none}.grecaptcha-badge{visibility:hidden} /*XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>*/.hljs {background: #fff;color: black; }/* Gray DOCTYPE selectors like WebKit */ .xml .hljs-meta {color: #c0c0c0; }.hljs-comment, .hljs-quote {color: #007400; }.hljs-tag, .hljs-attribute, .hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-name {color: #aa0d91; }.hljs-variable, .hljs-template-variable {color: #3F6E74; }.hljs-code, .hljs-string, .hljs-meta .hljs-string {color: #c41a16; }.hljs-regexp, .hljs-link {color: #0E0EFF; }.hljs-title, .hljs-symbol, .hljs-bullet, .hljs-number {color: #1c00cf; }.hljs-section, .hljs-meta {color: #643820; }.hljs-title.class_, .hljs-class .hljs-title, .hljs-type, .hljs-built_in, .hljs-params {color: #5c2699; }.hljs-attr {color: #836C28; }.hljs-subst {color: #000; }.hljs-formula {background-color: #eee;font-style: italic; }.hljs-addition {background-color: #baeeba; }.hljs-deletion {background-color: #ffc8bd; }.hljs-selector-id, .hljs-selector-class {color: #9b703f; }.hljs-doctag, .hljs-strong {font-weight: bold; }.hljs-emphasis {font-style: italic; } </style><style type="text/css" data-fela-rehydration="586" data-fela-type="KEYFRAME">@-webkit-keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}@-moz-keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}@keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE">.a{font-family:medium-content-sans-serif-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif}.b{font-weight:400}.c{background-color:rgba(255, 255, 255, 1)}.l{display:block}.m{position:sticky}.n{top:0}.o{z-index:500}.p{padding:0 24px}.q{align-items:center}.r{border-bottom:solid 1px #F2F2F2}.y{height:41px}.z{line-height:20px}.ab{display:flex}.ac{height:57px}.ae{flex:1 0 auto}.af{color:inherit}.ag{fill:inherit}.ah{font-size:inherit}.ai{border:inherit}.aj{font-family:inherit}.ak{letter-spacing:inherit}.al{font-weight:inherit}.am{padding:0}.an{margin:0}.ao{cursor:pointer}.ap:disabled{cursor:not-allowed}.aq:disabled{color:#6B6B6B}.ar:disabled{fill:#6B6B6B}.au{width:auto}.av path{fill:#242424}.aw{height:25px}.ax{margin-left:16px}.ay{border:none}.az{border-radius:20px}.ba{width:240px}.bb{background:#F9F9F9}.bc path{fill:#6B6B6B}.be{outline:none}.bf{font-family:sohne, "Helvetica Neue", Helvetica, Arial, sans-serif}.bg{font-size:14px}.bh{width:100%}.bi{padding:10px 20px 10px 0}.bj{background-color:transparent}.bk{color:#242424}.bl::placeholder{color:#6B6B6B}.bm{display:inline-block}.bn{margin-left:12px}.bo{margin-right:12px}.bp{border-radius:4px}.bq{margin-left:24px}.br{height:24px}.bx{background-color:#F9F9F9}.by{border-radius:50%}.bz{height:32px}.ca{width:32px}.cb{justify-content:center}.ch{max-width:680px}.ci{min-width:0}.cj{animation:k1 1.2s ease-in-out infinite}.ck{height:100vh}.cl{margin-bottom:16px}.cm{margin-top:48px}.cn{align-items:flex-start}.co{flex-direction:column}.cp{justify-content:space-between}.cq{margin-bottom:24px}.cw{width:80%}.cx{background-color:#F2F2F2}.dd{height:44px}.de{width:44px}.df{margin:auto 0}.dg{margin-bottom:4px}.dh{height:16px}.di{width:120px}.dj{width:80px}.dp{margin-bottom:8px}.dq{width:96%}.dr{width:98%}.ds{width:81%}.dt{margin-left:8px}.du{color:#6B6B6B}.dv{font-size:13px}.dw{height:100%}.ep{color:#FFFFFF}.eq{fill:#FFFFFF}.er{background:rgba(48, 150, 154, 1)}.es{border-color:rgba(48, 150, 154, 1)}.ew:disabled{cursor:inherit !important}.ex:disabled{opacity:0.3}.ey:disabled:hover{background:rgba(48, 150, 154, 1)}.ez:disabled:hover{border-color:rgba(48, 150, 154, 1)}.fa{border-radius:99em}.fb{border-width:1px}.fc{border-style:solid}.fd{box-sizing:border-box}.fe{text-decoration:none}.ff{text-align:center}.fi{margin-right:32px}.fj{position:relative}.fk{fill:#6B6B6B}.fn{background:transparent}.fo svg{margin-left:4px}.fp svg{fill:#6B6B6B}.fr{box-shadow:inset 0 0 0 1px rgba(0, 0, 0, 0.05)}.fs{position:absolute}.fz{margin:0 24px}.gd{background:rgba(255, 255, 255, 1)}.ge{border:1px solid #F2F2F2}.gf{box-shadow:0 1px 4px #F2F2F2}.gg{max-height:100vh}.gh{overflow-y:auto}.gi{left:0}.gj{top:calc(100vh + 100px)}.gk{bottom:calc(100vh + 100px)}.gl{width:10px}.gm{pointer-events:none}.gn{word-break:break-word}.go{word-wrap:break-word}.gp:after{display:block}.gq:after{content:""}.gr:after{clear:both}.gs{margin-left:auto}.gt{margin-right:auto}.gu{max-width:1440px}.ha{clear:both}.hc{cursor:zoom-in}.hd{z-index:auto}.hf{max-width:100%}.hg{height:auto}.hh{line-height:1.23}.hi{letter-spacing:0}.hj{font-style:normal}.hk{font-weight:700}.if{margin-bottom:-0.27em}.ig{line-height:1.394}.jb{align-items:baseline}.jc{width:48px}.jd{height:48px}.je{border:2px solid rgba(255, 255, 255, 1)}.jf{z-index:0}.jg{box-shadow:none}.jh{border:1px solid rgba(0, 0, 0, 0.05)}.ji{margin-left:-12px}.jj{width:28px}.jk{height:28px}.jl{z-index:1}.jm{width:24px}.jn{margin-bottom:2px}.jo{flex-wrap:nowrap}.jp{font-size:16px}.jq{line-height:24px}.js{margin:0 8px}.jt{display:inline}.ju{color:rgba(48, 150, 154, 1)}.jv{fill:rgba(48, 150, 154, 1)}.jy{flex:0 0 auto}.kb{flex-wrap:wrap}.ke{white-space:pre-wrap}.kf{margin-right:4px}.kg{overflow:hidden}.kh{max-height:20px}.ki{text-overflow:ellipsis}.kj{display:-webkit-box}.kk{-webkit-line-clamp:1}.kl{-webkit-box-orient:vertical}.km{word-break:break-all}.ko{padding-left:8px}.kp{padding-right:8px}.lq> *{flex-shrink:0}.lr{overflow-x:scroll}.ls::-webkit-scrollbar{display:none}.lt{scrollbar-width:none}.lu{-ms-overflow-style:none}.lv{width:74px}.lw{flex-direction:row}.lx{z-index:2}.ma{-webkit-user-select:none}.mb{border:0}.mc{fill:rgba(117, 117, 117, 1)}.mf{outline:0}.mg{user-select:none}.mh> svg{pointer-events:none}.mq{cursor:progress}.mr{margin-left:4px}.ms{margin-top:0px}.mt{opacity:1}.mu{padding:4px 0}.mx{width:16px}.mz{display:inline-flex}.nf{padding:8px 2px}.ng svg{color:#6B6B6B}.nx{line-height:1.58}.ny{letter-spacing:-0.004em}.nz{font-family:source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif}.os{margin-bottom:-0.46em}.ot{text-decoration:underline}.ou{line-height:1.12}.ov{letter-spacing:-0.022em}.ow{font-weight:600}.pp{margin-bottom:-0.28em}.pv{line-height:1.18}.qj{margin-bottom:-0.31em}.qk{list-style-type:decimal}.ql{margin-left:30px}.qm{padding-left:0px}.qs{max-width:1600px}.qy{margin-top:10px}.qz{max-width:728px}.rc{font-style:inherit}.rd{overflow-x:auto}.re{font-family:source-code-pro, Menlo, Monaco, "Courier New", Courier, monospace}.rf{padding:32px}.rg{border:1px solid #E5E5E5}.rh{line-height:1.4}.ri{margin-top:-0.2em}.rj{margin-bottom:-0.2em}.rk{white-space:pre}.rl{min-width:fit-content}.rm{font-style:italic}.rn{margin-bottom:26px}.ro{margin-top:6px}.rp{margin-top:8px}.rq{margin-right:8px}.rr{padding:8px 16px}.rs{border-radius:100px}.rt{transition:background 300ms ease}.rv{white-space:nowrap}.rw{border-top:none}.rx{margin-bottom:14px}.ry{height:52px}.rz{max-height:52px}.sa{box-sizing:content-box}.sb{position:static}.sd{max-width:155px}.sj{margin-right:20px}.sp{height:0px}.sq{margin-bottom:40px}.sr{margin-bottom:48px}.tf{border-radius:2px}.th{height:64px}.ti{width:64px}.tj{align-self:flex-end}.tk{flex:1 1 auto}.tq{padding-right:4px}.tr{font-weight:500}.ty{margin-top:16px}.tz{color:rgba(255, 255, 255, 1)}.ua{fill:rgba(255, 255, 255, 1)}.ub{background:rgba(25, 25, 25, 1)}.uc{border-color:rgba(25, 25, 25, 1)}.uf:disabled{opacity:0.1}.ug:disabled:hover{background:rgba(25, 25, 25, 1)}.uh:disabled:hover{border-color:rgba(25, 25, 25, 1)}.uq{gap:18px}.ur{fill:rgba(61, 61, 61, 1)}.ut{margin-top:32px}.uu{fill:#242424}.uv{background:0}.uw{border-color:#242424}.ux:disabled:hover{color:#242424}.uy:disabled:hover{fill:#242424}.uz:disabled:hover{border-color:#242424}.vk{border-bottom:solid 1px #E5E5E5}.vl{margin-top:72px}.vm{padding:24px 0}.vn{margin-bottom:0px}.vo{margin-right:16px}.as:hover:not(:disabled){color:rgba(25, 25, 25, 1)}.at:hover:not(:disabled){fill:rgba(25, 25, 25, 1)}.et:hover{background:rgba(51, 128, 131, 1)}.eu:hover{border-color:rgba(51, 128, 131, 1)}.ev:hover{cursor:pointer}.fl:hover{color:#242424}.fm:hover{fill:#242424}.fq:hover svg{fill:#242424}.ft:hover{background-color:rgba(0, 0, 0, 0.1)}.jr:hover{text-decoration:underline}.jw:hover:not(:disabled){color:rgba(51, 128, 131, 1)}.jx:hover:not(:disabled){fill:rgba(51, 128, 131, 1)}.me:hover{fill:rgba(8, 8, 8, 1)}.mv:hover{fill:#000000}.mw:hover p{color:#000000}.my:hover{color:#000000}.nh:hover svg{color:#000000}.ru:hover{background-color:#F2F2F2}.tg:hover{background-color:none}.ud:hover{background:#000000}.ue:hover{border-color:#242424}.us:hover{fill:rgba(25, 25, 25, 1)}.bd:focus-within path{fill:#242424}.he:focus{transform:scale(1.01)}.md:focus{fill:rgba(8, 8, 8, 1)}.ni:focus svg{color:#000000}.mi:active{border-style:none}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (min-width: 1080px)">.d{display:none}.bw{width:64px}.cg{margin:0 64px}.cv{height:48px}.dc{margin-bottom:52px}.do{margin-bottom:48px}.ef{font-size:14px}.eg{line-height:20px}.em{font-size:13px}.eo{padding:5px 12px}.fh{display:flex}.fy{margin-bottom:68px}.gc{max-width:680px}.gz{margin-top:40px}.ib{font-size:42px}.ic{margin-top:1em}.id{line-height:52px}.ie{letter-spacing:-0.011em}.it{font-size:22px}.iu{margin-top:0.92em}.iv{line-height:28px}.ja{align-items:center}.lc{border-top:solid 1px #F2F2F2}.ld{border-bottom:solid 1px #F2F2F2}.le{margin:32px 0 0}.lf{padding:3px 8px}.lo> *{margin-right:24px}.lp> :last-child{margin-right:0}.mp{margin-top:0px}.ne{margin:0}.oo{font-size:20px}.op{margin-top:2.14em}.oq{line-height:32px}.or{letter-spacing:-0.003em}.pl{font-size:24px}.pm{margin-top:1.95em}.pn{line-height:30px}.po{letter-spacing:-0.016em}.pu{margin-top:0.94em}.qg{margin-top:1.72em}.qh{line-height:24px}.qi{letter-spacing:0}.qr{margin-top:1.14em}.qx{margin-top:56px}.si{display:inline-block}.so{margin-bottom:104px}.ss{flex-direction:row}.sv{margin-bottom:0}.sw{margin-right:20px}.tl{max-width:500px}.um{margin-bottom:88px}.up{margin-bottom:72px}.ve{width:min-width}.vj{padding-top:72px}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (max-width: 1079.98px)">.e{display:none}.mo{margin-top:0px}.ra{margin-left:auto}.rb{text-align:center}.sh{display:inline-block}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (max-width: 903.98px)">.f{display:none}.mn{margin-top:0px}.sg{display:inline-block}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (max-width: 727.98px)">.g{display:none}.ml{margin-top:0px}.mm{margin-right:0px}.sf{display:inline-block}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (max-width: 551.98px)">.h{display:none}.s{display:flex}.t{justify-content:space-between}.bs{width:24px}.cc{margin:0 24px}.cr{height:40px}.cy{margin-bottom:44px}.dk{margin-bottom:32px}.dx{font-size:13px}.dy{line-height:20px}.eh{padding:0px 8px 1px}.fu{margin-bottom:4px}.gv{margin-top:32px}.hl{font-size:32px}.hm{margin-top:1.01em}.hn{line-height:38px}.ho{letter-spacing:-0.014em}.ih{font-size:18px}.ii{margin-top:0.79em}.ij{line-height:24px}.iw{align-items:flex-start}.jz{flex-direction:column}.kc{margin-bottom:2px}.kq{margin:24px -24px 0}.kr{padding:0}.lg> *{margin-right:8px}.lh> :last-child{margin-right:24px}.ly{margin-left:0px}.mj{margin-top:0px}.mk{margin-right:0px}.na{margin:0}.nj{border:1px solid #F2F2F2}.nk{border-radius:99em}.nl{padding:0px 16px 0px 12px}.nm{height:38px}.nn{align-items:center}.np svg{margin-right:8px}.oa{margin-top:1.56em}.ob{line-height:28px}.oc{letter-spacing:-0.003em}.ox{font-size:20px}.oy{margin-top:1.2em}.oz{letter-spacing:0}.pq{margin-top:0.67em}.pw{font-size:16px}.px{margin-top:1.23em}.qn{margin-top:1.34em}.qt{margin-top:40px}.se{display:inline-block}.sk{margin-bottom:96px}.td{margin-bottom:20px}.te{margin-right:0}.tp{max-width:100%}.ts{font-size:24px}.tt{line-height:30px}.tu{letter-spacing:-0.016em}.ui{margin-bottom:64px}.va{width:100%}.vf{padding-top:48px}.no:hover{border-color:#E5E5E5}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (min-width: 904px) and (max-width: 1079.98px)">.i{display:none}.bv{width:64px}.cf{margin:0 64px}.cu{height:48px}.db{margin-bottom:52px}.dn{margin-bottom:48px}.ed{font-size:14px}.ee{line-height:20px}.ek{font-size:13px}.el{padding:5px 12px}.fg{display:flex}.fx{margin-bottom:68px}.gb{max-width:680px}.gy{margin-top:40px}.hx{font-size:42px}.hy{margin-top:1em}.hz{line-height:52px}.ia{letter-spacing:-0.011em}.iq{font-size:22px}.ir{margin-top:0.92em}.is{line-height:28px}.iz{align-items:center}.ky{border-top:solid 1px #F2F2F2}.kz{border-bottom:solid 1px #F2F2F2}.la{margin:32px 0 0}.lb{padding:3px 8px}.lm> *{margin-right:24px}.ln> :last-child{margin-right:0}.nd{margin:0}.ok{font-size:20px}.ol{margin-top:2.14em}.om{line-height:32px}.on{letter-spacing:-0.003em}.ph{font-size:24px}.pi{margin-top:1.95em}.pj{line-height:30px}.pk{letter-spacing:-0.016em}.pt{margin-top:0.94em}.qd{margin-top:1.72em}.qe{line-height:24px}.qf{letter-spacing:0}.qq{margin-top:1.14em}.qw{margin-top:56px}.sn{margin-bottom:104px}.st{flex-direction:row}.sx{margin-bottom:0}.sy{margin-right:20px}.tm{max-width:500px}.ul{margin-bottom:88px}.uo{margin-bottom:72px}.vd{width:min-width}.vi{padding-top:72px}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (min-width: 728px) and (max-width: 903.98px)">.j{display:none}.w{display:flex}.x{justify-content:space-between}.bu{width:64px}.ce{margin:0 48px}.ct{height:48px}.da{margin-bottom:52px}.dm{margin-bottom:48px}.eb{font-size:13px}.ec{line-height:20px}.ej{padding:0px 8px 1px}.fw{margin-bottom:68px}.ga{max-width:680px}.gx{margin-top:40px}.ht{font-size:42px}.hu{margin-top:1em}.hv{line-height:52px}.hw{letter-spacing:-0.011em}.in{font-size:22px}.io{margin-top:0.92em}.ip{line-height:28px}.iy{align-items:center}.ku{border-top:solid 1px #F2F2F2}.kv{border-bottom:solid 1px #F2F2F2}.kw{margin:32px 0 0}.kx{padding:3px 8px}.lk> *{margin-right:24px}.ll> :last-child{margin-right:0}.nc{margin:0}.og{font-size:20px}.oh{margin-top:2.14em}.oi{line-height:32px}.oj{letter-spacing:-0.003em}.pd{font-size:24px}.pe{margin-top:1.95em}.pf{line-height:30px}.pg{letter-spacing:-0.016em}.ps{margin-top:0.94em}.qa{margin-top:1.72em}.qb{line-height:24px}.qc{letter-spacing:0}.qp{margin-top:1.14em}.qv{margin-top:56px}.sm{margin-bottom:104px}.su{flex-direction:row}.sz{margin-bottom:0}.ta{margin-right:20px}.tn{max-width:500px}.uk{margin-bottom:88px}.un{margin-bottom:72px}.vc{width:min-width}.vh{padding-top:72px}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="all and (min-width: 552px) and (max-width: 727.98px)">.k{display:none}.u{display:flex}.v{justify-content:space-between}.bt{width:24px}.cd{margin:0 24px}.cs{height:40px}.cz{margin-bottom:44px}.dl{margin-bottom:32px}.dz{font-size:13px}.ea{line-height:20px}.ei{padding:0px 8px 1px}.fv{margin-bottom:4px}.gw{margin-top:32px}.hp{font-size:32px}.hq{margin-top:1.01em}.hr{line-height:38px}.hs{letter-spacing:-0.014em}.ik{font-size:18px}.il{margin-top:0.79em}.im{line-height:24px}.ix{align-items:flex-start}.ka{flex-direction:column}.kd{margin-bottom:2px}.ks{margin:24px 0 0}.kt{padding:0}.li> *{margin-right:8px}.lj> :last-child{margin-right:8px}.lz{margin-left:0px}.nb{margin:0}.nq{border:1px solid #F2F2F2}.nr{border-radius:99em}.ns{padding:0px 16px 0px 12px}.nt{height:38px}.nu{align-items:center}.nw svg{margin-right:8px}.od{margin-top:1.56em}.oe{line-height:28px}.of{letter-spacing:-0.003em}.pa{font-size:20px}.pb{margin-top:1.2em}.pc{letter-spacing:0}.pr{margin-top:0.67em}.py{font-size:16px}.pz{margin-top:1.23em}.qo{margin-top:1.34em}.qu{margin-top:40px}.sl{margin-bottom:96px}.tb{margin-bottom:20px}.tc{margin-right:0}.to{max-width:100%}.tv{font-size:24px}.tw{line-height:30px}.tx{letter-spacing:-0.016em}.uj{margin-bottom:64px}.vb{width:100%}.vg{padding-top:48px}.nv:hover{border-color:#E5E5E5}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="print">.sc{display:none}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="(prefers-reduced-motion: no-preference)">.hb{transition:transform 300ms cubic-bezier(0.2, 0, 0.2, 1)}</style><style type="text/css" data-fela-rehydration="586" data-fela-type="RULE" media="(orientation: landscape) and (max-width: 903.98px)">.kn{max-height:none}</style></head><body><div id="root"><div class="a b c"><div class="d e f g h i j k"></div><script>document.domain = document.domain;</script><div class="l c"><div class="l m n o c"><div class="p q r s t u v w x i d y z"><a class="du ag dv bf ak b am an ao ap aq ar as at s u w i d q dw z" href="https://rsci.app.link/?%24canonical_url=https%3A%2F%2Fmedium.com%2Fp%2Fa784b2dbe325&%7Efeature=LoOpenInAppButton&%7Echannel=ShowPostUnderCollection&source=---top_nav_layout_nav----------------------------------" rel="noopener follow">Open in app<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="none" viewBox="0 0 10 10" class="dt"><path fill="currentColor" d="M.985 8.485a.375.375 0 1 0 .53.53zM8.75 1.25h.375A.375.375 0 0 0 8.75.875zM8.375 6.5a.375.375 0 1 0 .75 0zM3.5.875a.375.375 0 1 0 0 .75zm-1.985 8.14 7.5-7.5-.53-.53-7.5 7.5zm6.86-7.765V6.5h.75V1.25zM3.5 1.625h5.25v-.75H3.5z"></path></svg></a><div class="ab q"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><button class="bf b dx dy eh dz ea ei eb ec ej ek ee el em eg eo ep eq er es et eu ev ew ex ey ez fa fb fc fd bm fe ff" data-testid="headerSignUpButton">Sign up</button></span></p><div class="ax l"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSignInButton" rel="noopener follow" href="/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&source=post_page---top_nav_layout_nav-----------------------global_nav-----------">Sign in</a></span></p></div></div></div><div class="p q r ab ac"><div class="ab q ae"><a class="af ag ah ai aj ak al am an ao ap aq ar as at ab" aria-label="Homepage" data-testid="headerMediumLogo" rel="noopener follow" href="/?source=---top_nav_layout_nav----------------------------------"><svg xmlns="http://www.w3.org/2000/svg" width="719" height="160" fill="none" viewBox="0 0 719 160" class="au av aw"><path fill="#242424" d="m174.104 9.734.215-.047V8.02H130.39L89.6 103.89 48.81 8.021H1.472v1.666l.212.047c8.018 1.81 12.09 4.509 12.09 14.242V137.93c0 9.734-4.087 12.433-12.106 14.243l-.212.047v1.671h32.118v-1.665l-.213-.048c-8.018-1.809-12.089-4.509-12.089-14.242V30.586l52.399 123.305h2.972l53.925-126.743V140.75c-.687 7.688-4.721 10.062-11.982 11.701l-.215.05v1.652h55.948v-1.652l-.215-.05c-7.269-1.639-11.4-4.013-12.087-11.701l-.037-116.774h.037c0-9.733 4.071-12.432 12.087-14.242m25.555 75.488c.915-20.474 8.268-35.252 20.606-35.507 3.806.063 6.998 1.312 9.479 3.714 5.272 5.118 7.751 15.812 7.368 31.793zm-.553 5.77h65.573v-.275c-.186-15.656-4.721-27.834-13.466-36.196-7.559-7.227-18.751-11.203-30.507-11.203h-.263c-6.101 0-13.584 1.48-18.909 4.16-6.061 2.807-11.407 7.003-15.855 12.511-7.161 8.874-11.499 20.866-12.554 34.343q-.05.606-.092 1.212a50 50 0 0 0-.065 1.151 85.807 85.807 0 0 0-.094 5.689c.71 30.524 17.198 54.917 46.483 54.917 25.705 0 40.675-18.791 44.407-44.013l-1.886-.664c-6.557 13.556-18.334 21.771-31.738 20.769-18.297-1.369-32.314-19.922-31.042-42.395m139.722 41.359c-2.151 5.101-6.639 7.908-12.653 7.908s-11.513-4.129-15.418-11.63c-4.197-8.053-6.405-19.436-6.405-32.92 0-28.067 8.729-46.22 22.24-46.22 5.657 0 10.111 2.807 12.236 7.704zm43.499 20.008c-8.019-1.897-12.089-4.722-12.089-14.951V1.309l-48.716 14.353v1.757l.299-.024c6.72-.543 11.278.386 13.925 2.83 2.072 1.915 3.082 4.853 3.082 8.987v18.66c-4.803-3.067-10.516-4.56-17.448-4.56-14.059 0-26.909 5.92-36.176 16.672-9.66 11.205-14.767 26.518-14.767 44.278-.003 31.72 15.612 53.039 38.851 53.039 13.595 0 24.533-7.449 29.54-20.013v16.865h43.711v-1.746zM424.1 19.819c0-9.904-7.468-17.374-17.375-17.374-9.859 0-17.573 7.632-17.573 17.374s7.721 17.374 17.573 17.374c9.907 0 17.375-7.47 17.375-17.374m11.499 132.546c-8.019-1.897-12.089-4.722-12.089-14.951h-.035V43.635l-43.714 12.551v1.705l.263.024c9.458.842 12.047 4.1 12.047 15.152v81.086h43.751v-1.746zm112.013 0c-8.018-1.897-12.089-4.722-12.089-14.951V43.635l-41.621 12.137v1.71l.246.026c7.733.813 9.967 4.257 9.967 15.36v59.279c-2.578 5.102-7.415 8.131-13.274 8.336-9.503 0-14.736-6.419-14.736-18.073V43.638l-43.714 12.55v1.703l.262.024c9.459.84 12.05 4.097 12.05 15.152v50.17a56.3 56.3 0 0 0 .91 10.444l.787 3.423c3.701 13.262 13.398 20.197 28.59 20.197 12.868 0 24.147-7.966 29.115-20.43v17.311h43.714v-1.747zm169.818 1.788v-1.749l-.213-.05c-8.7-2.006-12.089-5.789-12.089-13.49v-63.79c0-19.89-11.171-31.761-29.883-31.761-13.64 0-25.141 7.882-29.569 20.16-3.517-13.01-13.639-20.16-28.606-20.16-13.146 0-23.449 6.938-27.869 18.657V43.643L545.487 55.68v1.715l.263.024c9.345.829 12.047 4.181 12.047 14.95v81.784h40.787v-1.746l-.215-.053c-6.941-1.631-9.181-4.606-9.181-12.239V66.998c1.836-4.289 5.537-9.37 12.853-9.37 9.086 0 13.692 6.296 13.692 18.697v77.828h40.797v-1.746l-.215-.053c-6.94-1.631-9.18-4.606-9.18-12.239V75.066a42 42 0 0 0-.578-7.26c1.947-4.661 5.86-10.177 13.475-10.177 9.214 0 13.691 6.114 13.691 18.696v77.828z"></path></svg></a><div class="ax h"><div class="ab ay az ba bb q bc bd"><div class="bm" aria-hidden="false" aria-describedby="searchResults" aria-labelledby="searchResults"></div><div class="bn bo ab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M4.092 11.06a6.95 6.95 0 1 1 13.9 0 6.95 6.95 0 0 1-13.9 0m6.95-8.05a8.05 8.05 0 1 0 5.13 14.26l3.75 3.75a.56.56 0 1 0 .79-.79l-3.73-3.73A8.05 8.05 0 0 0 11.042 3z" clip-rule="evenodd"></path></svg></div><input role="combobox" aria-controls="searchResults" aria-expanded="false" aria-label="search" data-testid="headerSearchInput" tabindex="0" class="ay be bf bg z bh bi bj bk bl" placeholder="Search" value=""/></div></div></div><div class="h k w fg fh"><div class="fi ab"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerWriteButton" rel="noopener follow" href="/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2Fnew-story&source=---top_nav_layout_nav-----------------------new_post_topnav-----------"><div class="bf b bg z du fj fk ab q fl fm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" aria-label="Write"><path fill="currentColor" d="M14 4a.5.5 0 0 0 0-1zm7 6a.5.5 0 0 0-1 0zm-7-7H4v1h10zM3 4v16h1V4zm1 17h16v-1H4zm17-1V10h-1v10zm-1 1a1 1 0 0 0 1-1h-1zM3 20a1 1 0 0 0 1 1v-1zM4 3a1 1 0 0 0-1 1h1z"></path><path stroke="currentColor" d="m17.5 4.5-8.458 8.458a.25.25 0 0 0-.06.098l-.824 2.47a.25.25 0 0 0 .316.316l2.47-.823a.25.25 0 0 0 .098-.06L19.5 6.5m-2-2 2.323-2.323a.25.25 0 0 1 .354 0l1.646 1.646a.25.25 0 0 1 0 .354L19.5 6.5m-2-2 2 2"></path></svg><div class="dt l">Write</div></div></a></span></div></div><div class="k j i d"><div class="fi ab"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSearchButton" rel="noopener follow" href="/search?source=---top_nav_layout_nav----------------------------------"><div class="bf b bg z du fj fk ab q fl fm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" aria-label="Search"><path fill="currentColor" fill-rule="evenodd" d="M4.092 11.06a6.95 6.95 0 1 1 13.9 0 6.95 6.95 0 0 1-13.9 0m6.95-8.05a8.05 8.05 0 1 0 5.13 14.26l3.75 3.75a.56.56 0 1 0 .79-.79l-3.73-3.73A8.05 8.05 0 0 0 11.042 3z" clip-rule="evenodd"></path></svg></div></a></div></div><div class="fi h k j"><div class="ab q"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><button class="bf b dx dy eh dz ea ei eb ec ej ek ee el em eg eo ep eq er es et eu ev ew ex ey ez fa fb fc fd bm fe ff" data-testid="headerSignUpButton">Sign up</button></span></p><div class="ax l"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSignInButton" rel="noopener follow" href="/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&source=post_page---top_nav_layout_nav-----------------------global_nav-----------">Sign in</a></span></p></div></div></div><div class="l" aria-hidden="false"><button class="ay fn am ab q ao fo fp fq" aria-label="user options menu" data-testid="headerUserIcon"><div class="l fj"><img alt="" class="l fd by bz ca cx" src="https://miro.medium.com/v2/resize:fill:64:64/1*dmbNkD5D-u45r44go_cf0g.png" width="32" height="32" loading="lazy" role="presentation"/><div class="fr by l bz ca fs n ay ft"></div></div></button></div></div></div><div class="l"><div class="fu fv fw fx fy l"><div class="ab cb"><div class="ci bh fz ga gb gc"></div></div><article><div class="l"><div class="l"><span class="l"></span><section><div><div class="fs gi gj gk gl gm"></div><div class="gn go gp gq gr"><div class="ab cb"><div class="ci bh fz ga gb gc"><figure class="gv gw gx gy gz ha gs gt paragraph-image"><div role="button" tabindex="0" class="hb hc fj hd bh he"><div class="gs gt gu"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 640w, https://miro.medium.com/v2/resize:fit:720/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 720w, https://miro.medium.com/v2/resize:fit:750/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 750w, https://miro.medium.com/v2/resize:fit:786/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 786w, https://miro.medium.com/v2/resize:fit:828/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 828w, https://miro.medium.com/v2/resize:fit:1100/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 1100w, https://miro.medium.com/v2/resize:fit:1400/1*uMA-yyBcSyRjQBwdQnbDdw.jpeg 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="A person making pesto sauce with a mortar and pestle" class="bh hf hg c" width="700" height="467" loading="eager"/></picture></div></div></figure><div><h1 id="cde1" class="pw-post-title hh hi hj bf hk hl hm hn ho hp hq hr hs ht hu hv hw hx hy hz ia ib ic id ie if bk" data-testid="storyTitle">Adopting Bazel for Web at Scale</h1></div><div><h2 id="dfaa" class="pw-subtitle-paragraph ig hi hj bf b ih ii ij ik il im in io ip iq ir is it iu iv cq du">How and Why We Migrated Airbnb’s Large-Scale Web Monorepo to Bazel</h2><div><div class="speechify-ignore ab cp"><div class="speechify-ignore bh l"><div class="iw ix iy iz ja ab"><div><div class="ab jb"><div><div class="bm" aria-hidden="false"><a rel="noopener follow" href="/@sharmila.jesupaul?source=post_page---byline--a784b2dbe325--------------------------------"><div class="l jc jd by je jf"><div class="l fj"><img alt="Sharmila Jesupaul" class="l fd by dd de cx" src="https://miro.medium.com/v2/resize:fill:88:88/1*oSKV94STFMJ-V0q6hJPUVA.png" width="44" height="44" loading="lazy" data-testid="authorPhoto"/><div class="jg by l dd de fs n jh ft"></div></div></div></a></div></div><div class="ji ab fj"><div><div class="bm" aria-hidden="false"><a href="https://medium.com/airbnb-engineering?source=post_page---byline--a784b2dbe325--------------------------------" rel="noopener follow"><div class="l jj jk by je jl"><div class="l fj"><img alt="The Airbnb Tech Blog" class="l fd by br jm cx" src="https://miro.medium.com/v2/resize:fill:48:48/1*MlNQKg-sieBGW5prWoe9HQ.jpeg" width="24" height="24" loading="lazy" data-testid="publicationPhoto"/><div class="jg by l br jm fs n jh ft"></div></div></div></a></div></div></div></div></div><div class="bn bh l"><div class="ab"><div style="flex:1"><span class="bf b bg z bk"><div class="jn ab q"><div class="ab q jo"><div class="ab q"><div><div class="bm" aria-hidden="false"><p class="bf b jp jq bk"><a class="af ag ah ai aj ak al am an ao ap aq ar jr" data-testid="authorName" rel="noopener follow" href="/@sharmila.jesupaul?source=post_page---byline--a784b2dbe325--------------------------------">Sharmila Jesupaul</a></p></div></div></div><span class="js jt" aria-hidden="true"><span class="bf b bg z du">·</span></span><p class="bf b jp jq du"><span><a class="ju jv ah ai aj ak al am an ao ap aq ar ex jw jx" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fsubscribe%2Fuser%2F1186193d899d&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&user=Sharmila+Jesupaul&userId=1186193d899d&source=post_page-1186193d899d--byline--a784b2dbe325---------------------post_header-----------">Follow</a></span></p></div></div></span></div></div><div class="l jy"><span class="bf b bg z du"><div class="ab cn jz ka kb"><div class="kc kd ab"><div class="bf b bg z du ab ke"><span class="kf l jy">Published in</span><div><div class="l" aria-hidden="false"><a class="af ag ah ai aj ak al am an ao ap aq ar jr ab q" data-testid="publicationName" href="https://medium.com/airbnb-engineering?source=post_page---byline--a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b bg z kg kh ki kj kk kl km kn bk">The Airbnb Tech Blog</p></a></div></div></div><div class="h k"><span class="js jt" aria-hidden="true"><span class="bf b bg z du">·</span></span></div></div><span class="bf b bg z du"><div class="ab ae"><span data-testid="storyReadTime">10 min read</span><div class="ko kp l" aria-hidden="true"><span class="l" aria-hidden="true"><span class="bf b bg z du">·</span></span></div><span data-testid="storyPublishDate">Nov 12, 2024</span></div></span></div></span></div></div></div><div class="ab cp kq kr ks kt ku kv kw kx ky kz la lb lc ld le lf"><div class="h k w fg fh q"><div class="lv l"><div class="ab q lw lx"><div class="pw-multi-vote-icon fj kf ly lz ma"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fairbnb-engineering%2Fa784b2dbe325&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&user=Sharmila+Jesupaul&userId=1186193d899d&source=---header_actions--a784b2dbe325---------------------clap_footer-----------"><div><div class="bm" aria-hidden="false"><div class="mb ao mc md me mf am mg mh mi ma"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l mj mk ml mm mn mo mp"><p class="bf b dv z du"><span class="mq">--</span></p></div></div></div><div><div class="bm" aria-hidden="false"><button class="ao mb mt mu ab q fk mv mw" aria-label="responses"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="ms"><path d="M18.006 16.803c1.533-1.456 2.234-3.325 2.234-5.321C20.24 7.357 16.709 4 12.191 4S4 7.357 4 11.482c0 4.126 3.674 7.482 8.191 7.482.817 0 1.622-.111 2.393-.327.231.2.48.391.744.559 1.06.693 2.203 1.044 3.399 1.044.224-.008.4-.112.486-.287a.49.49 0 0 0-.042-.518c-.495-.67-.845-1.364-1.04-2.057a4 4 0 0 1-.125-.598zm-3.122 1.055-.067-.223-.315.096a8 8 0 0 1-2.311.338c-4.023 0-7.292-2.955-7.292-6.587 0-3.633 3.269-6.588 7.292-6.588 4.014 0 7.112 2.958 7.112 6.593 0 1.794-.608 3.469-2.027 4.72l-.195.168v.255c0 .056 0 .151.016.295.025.231.081.478.154.733.154.558.398 1.117.722 1.659a5.3 5.3 0 0 1-2.165-.845c-.276-.176-.714-.383-.941-.59z"></path></svg><p class="bf b dv z du"><span class="pw-responses-count mr ms">4</span></p></button></div></div></div><div class="ab q lg lh li lj lk ll lm ln lo lp lq lr ls lt lu"><div class="mx k j i d"></div><div class="h k"><div><div class="bm" aria-hidden="false"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerBookmarkButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fa784b2dbe325&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&source=---header_actions--a784b2dbe325---------------------bookmark_footer-----------"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="none" viewBox="0 0 25 25" class="du my" aria-label="Add to list bookmark button"><path fill="currentColor" d="M18 2.5a.5.5 0 0 1 1 0V5h2.5a.5.5 0 0 1 0 1H19v2.5a.5.5 0 1 1-1 0V6h-2.5a.5.5 0 0 1 0-1H18zM7 7a1 1 0 0 1 1-1h3.5a.5.5 0 0 0 0-1H8a2 2 0 0 0-2 2v14a.5.5 0 0 0 .805.396L12.5 17l5.695 4.396A.5.5 0 0 0 19 21v-8.5a.5.5 0 0 0-1 0v7.485l-5.195-4.012a.5.5 0 0 0-.61 0L7 19.985z"></path></svg></a></span></div></div></div><div class="fd mz cn"><div class="l ae"><div class="ab cb"><div class="na nb nc nd ne hf ci bh"><div class="ab"><div class="bm bh" aria-hidden="false"><div><div class="bm" aria-hidden="false"><button aria-label="Listen" data-testid="audioPlayButton" class="af fk ah ai aj ak al nf an ao ap ex ng nh mw ni nj nk nl nm s nn no np nq nr ns nt u nu nv nw"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M3 12a9 9 0 1 1 18 0 9 9 0 0 1-18 0m9-10C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2m3.376 10.416-4.599 3.066a.5.5 0 0 1-.777-.416V8.934a.5.5 0 0 1 .777-.416l4.599 3.066a.5.5 0 0 1 0 .832" clip-rule="evenodd"></path></svg><div class="j i d"><p class="bf b bg z du">Listen</p></div></button></div></div></div></div></div></div></div></div><div class="bm" aria-hidden="false" aria-describedby="postFooterSocialMenu" aria-labelledby="postFooterSocialMenu"><div><div class="bm" aria-hidden="false"><button aria-controls="postFooterSocialMenu" aria-expanded="false" aria-label="Share Post" data-testid="headerSocialShareButton" class="af fk ah ai aj ak al nf an ao ap ex ng nh mw ni nj nk nl nm s nn no np nq nr ns nt u nu nv nw"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.218 4.931a.4.4 0 0 1-.118.132l.012.006a.45.45 0 0 1-.292.074.5.5 0 0 1-.3-.13l-2.02-2.02v7.07c0 .28-.23.5-.5.5s-.5-.22-.5-.5v-7.04l-2 2a.45.45 0 0 1-.57.04h-.02a.4.4 0 0 1-.16-.3.4.4 0 0 1 .1-.32l2.8-2.8a.5.5 0 0 1 .7 0l2.8 2.79a.42.42 0 0 1 .068.498m-.106.138.008.004v-.01zM16 7.063h1.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-11c-1.1 0-2-.9-2-2v-10a2 2 0 0 1 2-2H8a.5.5 0 0 1 .35.15.5.5 0 0 1 .15.35.5.5 0 0 1-.15.35.5.5 0 0 1-.35.15H6.4c-.5 0-.9.4-.9.9v10.2a.9.9 0 0 0 .9.9h11.2c.5 0 .9-.4.9-.9v-10.2c0-.5-.4-.9-.9-.9H16a.5.5 0 0 1 0-1" clip-rule="evenodd"></path></svg><div class="j i d"><p class="bf b bg z du">Share</p></div></button></div></div></div></div></div></div></div></div></div><p id="3dee" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk"><strong class="nz hk">By:</strong> <a class="af ot" href="https://www.linkedin.com/in/bbunge/" rel="noopener ugc nofollow" target="_blank">Brie Bunge</a> and <a class="af ot" href="https://www.linkedin.com/in/sharmilajesupaul/" rel="noopener ugc nofollow" target="_blank">Sharmila Jesupaul</a></p><h1 id="227d" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Introduction</h1><p id="ab69" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">At Airbnb, we’ve recently adopted <a class="af ot" href="https://bazel.build/" rel="noopener ugc nofollow" target="_blank">Bazel</a> — Google’s open source build tool–as our universal build system across backend, web, and <a class="af ot" rel="noopener" href="/airbnb-engineering/migrating-our-ios-build-system-from-buck-to-bazel-ddd6f3f25aa3">iOS</a> platforms. This post will cover our experience adopting Bazel for Airbnb’s large-scale (over 11 million lines of code) web monorepo. We’ll share how we prepared the code base, the principles that guided the migration, and the process of migrating selected CI jobs. Our goal is to share information that would have been valuable to us when we embarked on this journey and to contribute to the growing discussion around Bazel for web development.</p><h1 id="698d" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Why did we do this?</h1><p id="eb0e" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">Historically, we wrote bespoke build scripts and caching logic for various continuous integration (CI) jobs that proved challenging to maintain and consistently reached scaling limits as the repo grew. For example, our linter, <a class="af ot" href="https://eslint.org/" rel="noopener ugc nofollow" target="_blank">ESLint</a>, and TypeScript’s type checking did not support multi-threaded concurrency out-of-the-box. We extended our unit testing tool, <a class="af ot" href="https://jestjs.io/" rel="noopener ugc nofollow" target="_blank">Jest</a>, to be the runner for these tools because it had an API to leverage multiple workers.</p><p id="9ec5" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">It was not sustainable to continually create workarounds to overcome the inefficiencies of our tooling which did not support concurrency and we were incurring a long-run maintenance cost. To tackle these challenges and to best support our growing codebase, we found that Bazel’s sophistication, parallelism, caching, and performance fulfilled our needs.</p><p id="c7ba" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">Additionally, Bazel is language agnostic. This facilitated consolidation onto a single, universal build system across Airbnb and allowed us to share common infrastructure and expertise. Now, an engineer who works on our backend monorepo can switch to the web monorepo and know how to build and test things.</p><h1 id="7c6e" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Why was this hard?</h1><p id="2081" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">When we began the migration in 2021, there was no publicized industry precedent for integrating Bazel with web at scale outside of Google. Open source tooling didn’t work out-of-the-box, and leveraging <a class="af ot" href="https://bazel.build/remote/rbe" rel="noopener ugc nofollow" target="_blank">remote build execution</a> (RBE) introduced additional challenges. Our web codebase is large and contains many loose files, which led to performance issues when transmitting them to the remote environment. Additionally, we established migration principles that included improving or maintaining overall performance and reducing the impact on developers contributing to the monorepo during the transition. We effectively achieved both of these goals. Read on for more details.</p><h1 id="f50f" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Readying the Repository</h1><p id="8d95" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">We did some work up front to make the repository Bazel-ready–namely, cycle breaking and automated BUILD.bazel file generation.</p><h2 id="b949" class="pv ov hj bf ow pw px dy oz py pz ea pc og qa qb qc ok qd qe qf oo qg qh qi qj bk">Cycle Breaking</h2><p id="18d1" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">Our monorepo is laid out with projects under a top-level frontend/ directory. To start, we wanted to add BUILD.bazel files to each of the ~1000 top-level frontend directories. However, doing so created cycles in the dependency graph. This is not allowed in Bazel because there needs to be a <a class="af ot" href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" rel="noopener ugc nofollow" target="_blank">DAG</a> of build targets. Breaking these often felt like battling a hydra, as removing one cycle spawns more in its place. To accelerate the process, we modeled the problem as finding the <a class="af ot" href="https://en.wikipedia.org/wiki/Feedback_arc_set" rel="noopener ugc nofollow" target="_blank">minimum feedback arc set (MFAS)</a>¹ to identify the minimal set of edges to remove leaving a <a class="af ot" href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" rel="noopener ugc nofollow" target="_blank">DAG</a>. This set presented the least disruption, level of effort, and surfaced pathological edges.</p><h2 id="6725" class="pv ov hj bf ow pw px dy oz py pz ea pc og qa qb qc ok qd qe qf oo qg qh qi qj bk">Automated BUILD.bazel Generation</h2><p id="fd5c" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">We automatically generate BUILD.bazel files for the following reasons:</p><ol class=""><li id="80c7" class="nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os qk ql qm bk">Most contents are knowable from statically analyzable import / require statements.</li><li id="915f" class="nx ny hj nz b ih qn ob oc ik qo oe of og qp oi oj ok qq om on oo qr oq or os qk ql qm bk">Automation allowed us to quickly iterate on BUILD.bazel changes as we refined our rule definitions.</li><li id="bc18" class="nx ny hj nz b ih qn ob oc ik qo oe of og qp oi oj ok qq om on oo qr oq or os qk ql qm bk">It would take time for the migration to complete and we didn’t want to ask users to keep these files up-to-date when they weren’t yet gaining value from them.</li><li id="104c" class="nx ny hj nz b ih qn ob oc ik qo oe of og qp oi oj ok qq om on oo qr oq or os qk ql qm bk">Manually keeping these files up-to-date would constitute an additional Bazel tax, regressing the developer experience.</li></ol><p id="8ed0" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">We have a CLI tool called sync-configs that generates dependency-based configurations in the monorepo (e.g., tsconfig.json, project configuration, now BUILD.bazel). It uses <a class="af ot" href="https://github.com/jestjs/jest/tree/main/packages/jest-haste-map" rel="noopener ugc nofollow" target="_blank">jest-haste-map</a> and <a class="af ot" href="https://facebook.github.io/watchman/" rel="noopener ugc nofollow" target="_blank">watchman</a> with a custom version of the <a class="af ot" href="https://github.com/jestjs/jest/blob/main/packages/jest-haste-map/src/lib/dependencyExtractor.ts" rel="noopener ugc nofollow" target="_blank">dependencyExtractor</a> to determine the file-level dependency graph and part of <a class="af ot" href="https://github.com/bazelbuild/bazel-gazelle" rel="noopener ugc nofollow" target="_blank">Gazelle</a> to emit BUILD.bazel files. This CLI tool is similar to <a class="af ot" href="https://github.com/bazelbuild/bazel-gazelle" rel="noopener ugc nofollow" target="_blank">Gazelle</a> but also generates additional web specific configuration files such as tsconfig.json files used in TypeScript compilation.</p><h1 id="234e" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">CI Migration</h1><p id="5b70" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">With preparation work complete, we proceeded to migrate CI jobs to Bazel. This was a massive undertaking, so we divided the work into incremental milestones. We audited our CI jobs and chose to migrate the ones that would benefit the most: type checking, linting, and unit testing². To reduce the burden on our developers, we assigned the central Web Platform team the responsibility for porting CI jobs to Bazel. We proceeded one job at a time to deliver incremental value to developers sooner, gain confidence in our approach, focus our efforts, and build momentum. With each job, we ensured that the developer experience was high-quality, that performance improved, CI failures were reproducible locally, and that the tooling Bazel replaced was fully deprecated and removed.</p><h1 id="3986" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Enabling TypeScript</h1><p id="a5df" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">We started with the TypeScript (TS) CI job. We first tried the open source <a class="af ot" href="https://github.com/bazelbuild/rules_nodejs/blob/5.x/nodejs/private/ts_project.bzl" rel="noopener ugc nofollow" target="_blank">ts_project rule</a>³. However, it didn’t work well with RBE due to the sheer number of inputs, so we wrote a <a class="af ot" href="https://gist.github.com/brieb/8439c7869fa058554c58377fb52a3c84" rel="noopener ugc nofollow" target="_blank">custom rule</a> to reduce the number and size of the inputs.</p><p id="6ae1" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">The biggest source of inputs came from <a class="af ot" href="https://a0.muscache.com/im/pictures/airbnb-platform-assets/AirbnbPlatformAssets-Bazel%20blogpost/original/6826576e-79dc-4382-bc37-a62d9be3f597.png" rel="noopener ugc nofollow" target="_blank">node_modules</a>. Prior to this, the files for each npm package were being uploaded individually. Since Bazel works well with Java, we packaged up a full tar and a TS-specific tar (only containing the *.ts and package.json) for each npm package along the lines of Java JAR files (essentially zips).</p><p id="5f2f" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">Another source of inputs came through transitive dependencies. Transitive node_modules and d.ts files in the sandbox were being included because technically they can be needed for subsequent project compilations. For example, suppose project foo depends on bar, and types from bar are exposed in foo’s emit. As a result, project baz which depends on foo would also need bar’s outputs in the sandbox. For long chains of dependencies, this can bloat the inputs significantly with files that aren’t actually needed. TypeScript has a <a class="af ot" href="https://www.typescriptlang.org/tsconfig/#listFiles" rel="noopener ugc nofollow" target="_blank">— listFiles flag</a> that tells us which files are part of the compilation. We can package up this limited set of files along with the emitted d.ts files into an output tsc.tar.gz file⁴. With this, targets need only include direct dependencies, rather than all transitive dependencies⁵.</p><figure class="qt qu qv qw qx ha gs gt paragraph-image"><div role="button" tabindex="0" class="hb hc fj hd bh he"><div class="gs gt qs"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*cKjuiMnl5KNyCRgG 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*cKjuiMnl5KNyCRgG 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*cKjuiMnl5KNyCRgG 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*cKjuiMnl5KNyCRgG 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*cKjuiMnl5KNyCRgG 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*cKjuiMnl5KNyCRgG 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*cKjuiMnl5KNyCRgG 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*cKjuiMnl5KNyCRgG 640w, https://miro.medium.com/v2/resize:fit:720/0*cKjuiMnl5KNyCRgG 720w, https://miro.medium.com/v2/resize:fit:750/0*cKjuiMnl5KNyCRgG 750w, https://miro.medium.com/v2/resize:fit:786/0*cKjuiMnl5KNyCRgG 786w, https://miro.medium.com/v2/resize:fit:828/0*cKjuiMnl5KNyCRgG 828w, https://miro.medium.com/v2/resize:fit:1100/0*cKjuiMnl5KNyCRgG 1100w, https://miro.medium.com/v2/resize:fit:1400/0*cKjuiMnl5KNyCRgG 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh hf hg c" width="700" height="308" loading="lazy" role="presentation"/></picture></div></div><figcaption class="qy ff qz gs gt ra rb bf b bg z du"><em class="rc">Diagram showing how we use tars and the — listFiles flag to prune inputs/outputs of :types targets</em></figcaption></figure><p id="e8de" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">This custom rule unblocked switching to Bazel for TypeScript, as the job was now well under our CI runtime budget.</p><figure class="qt qu qv qw qx ha gs gt paragraph-image"><div role="button" tabindex="0" class="hb hc fj hd bh he"><div class="gs gt qs"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*BJB0TroGRohVvjAS 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*BJB0TroGRohVvjAS 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*BJB0TroGRohVvjAS 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*BJB0TroGRohVvjAS 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*BJB0TroGRohVvjAS 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*BJB0TroGRohVvjAS 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*BJB0TroGRohVvjAS 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*BJB0TroGRohVvjAS 640w, https://miro.medium.com/v2/resize:fit:720/0*BJB0TroGRohVvjAS 720w, https://miro.medium.com/v2/resize:fit:750/0*BJB0TroGRohVvjAS 750w, https://miro.medium.com/v2/resize:fit:786/0*BJB0TroGRohVvjAS 786w, https://miro.medium.com/v2/resize:fit:828/0*BJB0TroGRohVvjAS 828w, https://miro.medium.com/v2/resize:fit:1100/0*BJB0TroGRohVvjAS 1100w, https://miro.medium.com/v2/resize:fit:1400/0*BJB0TroGRohVvjAS 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh hf hg c" width="700" height="224" loading="lazy" role="presentation"/></picture></div></div><figcaption class="qy ff qz gs gt ra rb bf b bg z du"><em class="rc">Bar chart showing the speed up from switching to using our custom genrule</em></figcaption></figure><h1 id="74c1" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Enabling ESLint</h1><p id="56cc" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">We migrated the <a class="af ot" href="https://eslint.org/" rel="noopener ugc nofollow" target="_blank">ESLint</a> job next. Bazel works best with actions that are independent and have a narrow set of inputs. Some of our lint rules (e.g., special internal rules, <a class="af ot" href="https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/export.md" rel="noopener ugc nofollow" target="_blank">import/export</a>, <a class="af ot" href="https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/extensions.md" rel="noopener ugc nofollow" target="_blank">import/extensions</a>) inspected files outside of the linted file. We restricted our lint rules to those that could operate in isolation as a way of reducing input size and having only to lint directly affected files. This meant moving or deleting lint rules (e.g., those that were made redundant with TypeScript). As a result, we reduced CI times by over 70%.</p><figure class="qt qu qv qw qx ha gs gt paragraph-image"><div role="button" tabindex="0" class="hb hc fj hd bh he"><div class="gs gt qs"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*jxLe6RvjzyINaahq 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*jxLe6RvjzyINaahq 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*jxLe6RvjzyINaahq 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*jxLe6RvjzyINaahq 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*jxLe6RvjzyINaahq 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*jxLe6RvjzyINaahq 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*jxLe6RvjzyINaahq 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*jxLe6RvjzyINaahq 640w, https://miro.medium.com/v2/resize:fit:720/0*jxLe6RvjzyINaahq 720w, https://miro.medium.com/v2/resize:fit:750/0*jxLe6RvjzyINaahq 750w, https://miro.medium.com/v2/resize:fit:786/0*jxLe6RvjzyINaahq 786w, https://miro.medium.com/v2/resize:fit:828/0*jxLe6RvjzyINaahq 828w, https://miro.medium.com/v2/resize:fit:1100/0*jxLe6RvjzyINaahq 1100w, https://miro.medium.com/v2/resize:fit:1400/0*jxLe6RvjzyINaahq 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh hf hg c" width="700" height="231" loading="lazy" role="presentation"/></picture></div></div><figcaption class="qy ff qz gs gt ra rb bf b bg z du"><em class="rc">Time series graph showing the runtime speed-up in early May from only running ESLint on directly affected targets</em></figcaption></figure><h1 id="c4df" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Enabling Jest</h1><p id="5a30" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">Our next challenge was enabling <a class="af ot" href="https://jestjs.io" rel="noopener ugc nofollow" target="_blank">Jest</a>. This presented unique challenges, as we needed to bring along a much larger set of first and third-party dependencies, and there were more Bazel-specific failures to fix.</p><h2 id="59b6" class="pv ov hj bf ow pw px dy oz py pz ea pc og qa qb qc ok qd qe qf oo qg qh qi qj bk">Worker and Docker Cache</h2><p id="6263" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">We tarred up dependencies to reduce input size, but extraction was still slow. To address this, we introduced caching. One layer of cache is on the remote worker and another is on the worker’s Docker container, baked into the image at build time. The Docker layer exists to avoid losing our cache when remote workers are auto-scaled. We run a cron job once a week to update the Docker image with the newest set of cached dependencies, striking a balance of keeping them fresh while avoiding image thrashing. For more details, check out <a class="af ot" href="https://blog.engflow.com/2023/06/01/bazel-community-day--san-francisco/#taming-node_modules-in-rbe-airbnbs-journey-sharmila-jesupaul-airbnb" rel="noopener ugc nofollow" target="_blank">this Bazel Community Day talk</a>.</p><figure class="qt qu qv qw qx ha gs gt paragraph-image"><div role="button" tabindex="0" class="hb hc fj hd bh he"><div class="gs gt qs"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*e7zywi2UKNMa9qTF 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*e7zywi2UKNMa9qTF 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*e7zywi2UKNMa9qTF 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*e7zywi2UKNMa9qTF 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*e7zywi2UKNMa9qTF 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*e7zywi2UKNMa9qTF 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*e7zywi2UKNMa9qTF 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*e7zywi2UKNMa9qTF 640w, https://miro.medium.com/v2/resize:fit:720/0*e7zywi2UKNMa9qTF 720w, https://miro.medium.com/v2/resize:fit:750/0*e7zywi2UKNMa9qTF 750w, https://miro.medium.com/v2/resize:fit:786/0*e7zywi2UKNMa9qTF 786w, https://miro.medium.com/v2/resize:fit:828/0*e7zywi2UKNMa9qTF 828w, https://miro.medium.com/v2/resize:fit:1100/0*e7zywi2UKNMa9qTF 1100w, https://miro.medium.com/v2/resize:fit:1400/0*e7zywi2UKNMa9qTF 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh hf hg c" width="700" height="396" loading="lazy" role="presentation"/></picture></div></div><figcaption class="qy ff qz gs gt ra rb bf b bg z du"><em class="rc">Diagram showing symlinked npm dependencies to a Docker cache and worker cache</em></figcaption></figure><p id="dd3c" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">This added caching provided us with a ~25% speed up of our Jest unit testing CI job overall and reduced the time to extract our dependencies from 1–3 minutes to 3–7 seconds per target. This implementation required us to enable the NodeJS <a class="af ot" href="https://nodejs.org/api/cli.html#--preserve-symlinks" rel="noopener ugc nofollow" target="_blank">preserve-symlinks</a> option and patch some of our tools that followed symlinks to their real paths. We extended this caching strategy to our <a class="af ot" href="https://github.com/jestjs/jest/tree/main/packages/babel-jest" rel="noopener ugc nofollow" target="_blank">Babel</a> transformation cache, another source of poor performance.</p><h2 id="d47d" class="pv ov hj bf ow pw px dy oz py pz ea pc og qa qb qc ok qd qe qf oo qg qh qi qj bk">Implicit Dependencies</h2><p id="d5ff" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">Next, we needed to fix Bazel-specific test failures. Most of these were due to missing files. For any inputs not statically analyzable (e.g., referenced as a string without an import, babel plugin string referenced in .babelrc), we added support for a Bazel keep comment (e.g., // bazelKeep: path/to/file) which acts as though the file were imported. The advantages of this approach are:</p><p id="e5ba" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">1. It is colocated with the code that uses the dependency,</p><p id="55e0" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">2. BUILD.bazel files don’t need to be manually edited to add/move <a class="af ot" href="https://github.com/bazelbuild/bazel-gazelle?tab=readme-ov-file#keep-comments" rel="noopener ugc nofollow" target="_blank"># keep comments</a>,</p><p id="f6e2" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">3. There is no effect on runtime.</p><p id="028e" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">A small number of tests were unsuitable for Bazel because they required a large view of the repository or a dynamic and implicit set of dependencies. We moved these tests out of our unit testing job to separate CI checks.</p><h2 id="3737" class="pv ov hj bf ow pw px dy oz py pz ea pc og qa qb qc ok qd qe qf oo qg qh qi qj bk">Preventing Backsliding</h2><p id="87da" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">With over 20,000 test files and hundreds of people actively working in the same repository, we needed to pursue test fixes such that they would not be undone as product development progressed.</p><p id="9301" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">Our CI has three types of build queues:</p><p id="128e" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">1. “Required”, which blocks changes,</p><p id="9ed5" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">2. “Optional”, which is non-blocking,</p><p id="24b8" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">3. “Hidden”, which is non-blocking and not shown on PRs.</p><p id="d527" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">As we fixed tests, we moved them from “hidden” to “required” via a rule attribute. To ensure a single source of truth, tests run in “required” under Bazel were not run under the Jest setup being replaced.</p><pre class="qt qu qv qw qx rd re rf bp rg bb bk"><span id="c531" class="rh ov hj re b bg ri rj l rk rl"># frontend/app/script/__tests__/BUILD.bazel<br/>jest_test(<br/> name = "jest_test",<br/> is_required = True, # makes this target a required check on pull requests <br/> deps = [<br/> ":source_library",<br/> ],<br/>)</span></pre><p id="9878" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk"><em class="rm">Example jest_test rule. This signifies that this target will run on the “required” build queue.</em></p><p id="347e" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">We wrote a script comparing before and after Bazel to determine migration-readiness, using the metrics of test runtime, code coverage stats, and failure rate. Fortunately, the bulk of tests could be enabled without additional changes, so we enabled these in batches. We divided and conquered the remaining burndown list of failures with the central team, Web Platform, fixing and updating tests in Bazel to avoid putting this burden on our developers. After a grace period, we fully disabled and deleted the non-Bazel Jest infrastructure and removed the is_required param.</p><h1 id="8272" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Local Bazel Experience</h1><p id="1574" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">In tandem with our CI migration, we ensured that developers can run Bazel locally to reproduce and iterate on CI failures. Our migration principles included delivering only what was on par with or superior to the existing developer experience and performance. JavaScript tools have developer-friendly CLI experiences (e.g., watch mode, targeting select files, rich interactivity) and IDE integrations that we wanted to retain. By default, frontend developers can continue using the tools they know and love, and in cases where it is beneficial they can opt into Bazel. Discrepancies between Bazel and non-Bazel are rare and when they do occur, developers have a means of resolving the issue. For example, developers can run a single script, failed-on-pr which will re-run any targets failing CI locally to easily reproduce issues.</p><figure class="qt qu qv qw qx ha gs gt paragraph-image"><div role="button" tabindex="0" class="hb hc fj hd bh he"><div class="gs gt qs"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*IqcxStamyg_zPexr 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*IqcxStamyg_zPexr 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*IqcxStamyg_zPexr 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*IqcxStamyg_zPexr 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*IqcxStamyg_zPexr 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*IqcxStamyg_zPexr 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/0*IqcxStamyg_zPexr 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*IqcxStamyg_zPexr 640w, https://miro.medium.com/v2/resize:fit:720/0*IqcxStamyg_zPexr 720w, https://miro.medium.com/v2/resize:fit:750/0*IqcxStamyg_zPexr 750w, https://miro.medium.com/v2/resize:fit:786/0*IqcxStamyg_zPexr 786w, https://miro.medium.com/v2/resize:fit:828/0*IqcxStamyg_zPexr 828w, https://miro.medium.com/v2/resize:fit:1100/0*IqcxStamyg_zPexr 1100w, https://miro.medium.com/v2/resize:fit:1400/0*IqcxStamyg_zPexr 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh hf hg c" width="700" height="339" loading="lazy" role="presentation"/></picture></div></div><figcaption class="qy ff qz gs gt ra rb bf b bg z du"><em class="rc">Annotations on a failing build with scripts to recreate the failures, e.g. yak script jest:failed-on-pr</em></figcaption></figure><p id="3f8f" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">We also do some normalization of platform specific binaries so that we can reuse the cache between Linux and MacOS builds. This speeds up local development and CI jobs by sharing cache between a local developer’s macbook and linux machines in CI. For native npm packages (<a class="af ot" href="https://github.com/nodejs/node-gyp" rel="noopener ugc nofollow" target="_blank">node-gyp</a> dependencies) we exclude platform-specific files and build the package on the execution machine. The execution machine will be the machine executing the test or build process. We also use “universal binaries” (e.g., for node and zstd), where all platform binaries are included as inputs (so that inputs are consistent no matter which platform the action is run from) and the proper binary is <a class="af ot" href="https://gist.github.com/brieb/3c0fdb614122e928b4546c5d85c97ab3" rel="noopener ugc nofollow" target="_blank">chosen at runtime</a>.</p><h1 id="1f29" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Conclusion</h1><p id="b136" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">Adopting Bazel for our core CI jobs yielded significant performance improvements for TypeScript type checking (34% faster), ESLint linting (35% faster), and Jest unit tests (42% faster incremental runs, 29% overall). Moreover, our CI can now better scale as the repo grows.</p><p id="55c4" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">Next, to further improve Bazel performance, we will be focusing on persisting a warm Bazel host across CI runs, taming our build graph, powering CI jobs that do not use Bazel with the Bazel build graph, and potentially exploring <a class="af ot" href="https://en.wikipedia.org/wiki/SquashFS" rel="noopener ugc nofollow" target="_blank">SquashFS</a> to further compress and optimize our Bazel sandboxes.</p><p id="63a9" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">We hope that sharing our journey has provided insights for organizations considering a Bazel migration for web.</p><h1 id="9171" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">Acknowledgments</h1><p id="453a" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">Thank you Madison Capps, Meghan Dow, Matt Insler, Janusz Kudelka, Joe Lencioni, Rae Liu, James Robinson, Joel Snyder, Elliott Sprehn, Fanying Ye, and various other internal and external partners who helped bring Bazel to Airbnb.</p><p id="980b" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">We are also grateful to the broader Bazel community for being welcoming and sharing ideas.</p><h1 id="65fa" class="ou ov hj bf ow ox oy ij oz pa pb im pc pd pe pf pg ph pi pj pk pl pm pn po pp bk">****************</h1><p id="c4da" class="pw-post-body-paragraph nx ny hj nz b ih pq ob oc ik pr oe of og ps oi oj ok pt om on oo pu oq or os gn bk">[1]: This problem is <a class="af ot" href="https://en.wikipedia.org/wiki/NP-completeness" rel="noopener ugc nofollow" target="_blank">NP-complete</a>, though approximation algorithms have been devised that still guarantee no cycles; we chose the <a class="af ot" href="https://github.com/zhenv5/breaking_cycles_in_noisy_hierarchies" rel="noopener ugc nofollow" target="_blank">implementation</a> outlined in “<a class="af ot" href="https://dl.acm.org/doi/pdf/10.1145/3091478.3091495" rel="noopener ugc nofollow" target="_blank">Breaking Cycles in Noisy Hierarchies</a>”.</p><p id="2d1c" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">[2]: After initial evaluation, we considered migrating web asset bundling as out of scope (though we may revisit this in the future) due to high level of effort, unknowns in the bundler landscape, and neutral return on investment given our recent adoption of <a class="af ot" rel="noopener" href="/airbnb-engineering/faster-javascript-builds-with-metro-cfc46d617a1f">Metro</a>, as Metro’s architecture already factors in scalability features (e.g. parallelism, local and remote caching, and incremental builds).</p><p id="b864" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">[3]: There are newer TS rules that may work well for you <a class="af ot" href="https://github.com/aspect-build/rules_ts" rel="noopener ugc nofollow" target="_blank">here</a>.</p><p id="3098" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">[4]: We later switched to using <a class="af ot" href="https://github.com/facebook/zstd" rel="noopener ugc nofollow" target="_blank">zstd</a> instead of gzip because it produces archives that are better compressed and more deterministic, keeping tarballs consistent across different platforms.</p><p id="79f9" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk">[5]: While unnecessary files may still be included, it’s a much narrower set (and could be pruned as a further optimization).</p><p id="a7cd" class="pw-post-body-paragraph nx ny hj nz b ih oa ob oc ik od oe of og oh oi oj ok ol om on oo op oq or os gn bk"><em class="rm">All product names, logos, and brands are property of their respective owners. All company, product and service names used in this website are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.</em></p></div></div></div></div></section></div></div></article></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="rn ro ab kb"><div class="rp ab"><a class="rq ay am ao" rel="noopener follow" href="/tag/bazel?source=post_page-----a784b2dbe325--------------------------------"><div class="rr fj cx rs ge rt ru bf b bg z bk rv">Bazel</div></a></div><div class="rp ab"><a class="rq ay am ao" rel="noopener follow" href="/tag/web?source=post_page-----a784b2dbe325--------------------------------"><div class="rr fj cx rs ge rt ru bf b bg z bk rv">Web</div></a></div><div class="rp ab"><a class="rq ay am ao" rel="noopener follow" href="/tag/typescript?source=post_page-----a784b2dbe325--------------------------------"><div class="rr fj cx rs ge rt ru bf b bg z bk rv">Typescript</div></a></div><div class="rp ab"><a class="rq ay am ao" rel="noopener follow" href="/tag/migration?source=post_page-----a784b2dbe325--------------------------------"><div class="rr fj cx rs ge rt ru bf b bg z bk rv">Migration</div></a></div><div class="rp ab"><a class="rq ay am ao" rel="noopener follow" href="/tag/engineering?source=post_page-----a784b2dbe325--------------------------------"><div class="rr fj cx rs ge rt ru bf b bg z bk rv">Engineering</div></a></div></div></div></div><div class="l"></div><footer class="rw rx ry rz sa ab q sb jl c"><div class="l ae"><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="ab cp sc"><div class="ab q lw"><div class="sd l"><span class="l se sf sg e d"><div class="ab q lw lx"><div class="pw-multi-vote-icon fj kf ly lz ma"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fairbnb-engineering%2Fa784b2dbe325&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&user=Sharmila+Jesupaul&userId=1186193d899d&source=---footer_actions--a784b2dbe325---------------------clap_footer-----------"><div><div class="bm" aria-hidden="false"><div class="mb ao mc md me mf am mg mh mi ma"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l mj mk ml mm mn mo mp"><p class="bf b dv z du"><span class="mq">--</span></p></div></div></span><span class="l h g f sh si"><div class="ab q lw lx"><div class="pw-multi-vote-icon fj kf ly lz ma"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fairbnb-engineering%2Fa784b2dbe325&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&user=Sharmila+Jesupaul&userId=1186193d899d&source=---footer_actions--a784b2dbe325---------------------clap_footer-----------"><div><div class="bm" aria-hidden="false"><div class="mb ao mc md me mf am mg mh mi ma"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l mj mk ml mm mn mo mp"><p class="bf b dv z du"><span class="mq">--</span></p></div></div></span></div><div class="bq ab"><div><div class="bm" aria-hidden="false"><button class="ao mb mt mu ab q fk mv mw" aria-label="responses"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="ms"><path d="M18.006 16.803c1.533-1.456 2.234-3.325 2.234-5.321C20.24 7.357 16.709 4 12.191 4S4 7.357 4 11.482c0 4.126 3.674 7.482 8.191 7.482.817 0 1.622-.111 2.393-.327.231.2.48.391.744.559 1.06.693 2.203 1.044 3.399 1.044.224-.008.4-.112.486-.287a.49.49 0 0 0-.042-.518c-.495-.67-.845-1.364-1.04-2.057a4 4 0 0 1-.125-.598zm-3.122 1.055-.067-.223-.315.096a8 8 0 0 1-2.311.338c-4.023 0-7.292-2.955-7.292-6.587 0-3.633 3.269-6.588 7.292-6.588 4.014 0 7.112 2.958 7.112 6.593 0 1.794-.608 3.469-2.027 4.72l-.195.168v.255c0 .056 0 .151.016.295.025.231.081.478.154.733.154.558.398 1.117.722 1.659a5.3 5.3 0 0 1-2.165-.845c-.276-.176-.714-.383-.941-.59z"></path></svg><p class="bf b bg z du"><span class="pw-responses-count mr ms">4</span></p></button></div></div></div></div><div class="ab q"><div class="sj l jy"><div><div class="bm" aria-hidden="false"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerBookmarkButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fa784b2dbe325&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fairbnb-engineering%2Fadopting-bazel-for-web-at-scale-a784b2dbe325&source=---footer_actions--a784b2dbe325---------------------bookmark_footer-----------"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="none" viewBox="0 0 25 25" class="du my" aria-label="Add to list bookmark button"><path fill="currentColor" d="M18 2.5a.5.5 0 0 1 1 0V5h2.5a.5.5 0 0 1 0 1H19v2.5a.5.5 0 1 1-1 0V6h-2.5a.5.5 0 0 1 0-1H18zM7 7a1 1 0 0 1 1-1h3.5a.5.5 0 0 0 0-1H8a2 2 0 0 0-2 2v14a.5.5 0 0 0 .805.396L12.5 17l5.695 4.396A.5.5 0 0 0 19 21v-8.5a.5.5 0 0 0-1 0v7.485l-5.195-4.012a.5.5 0 0 0-.61 0L7 19.985z"></path></svg></a></span></div></div></div><div class="sj l jy"><div class="bm" aria-hidden="false" aria-describedby="postFooterSocialMenu" aria-labelledby="postFooterSocialMenu"><div><div class="bm" aria-hidden="false"><button aria-controls="postFooterSocialMenu" aria-expanded="false" aria-label="Share Post" data-testid="footerSocialShareButton" class="af fk ah ai aj ak al nf an ao ap ex ng nh mw ni"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.218 4.931a.4.4 0 0 1-.118.132l.012.006a.45.45 0 0 1-.292.074.5.5 0 0 1-.3-.13l-2.02-2.02v7.07c0 .28-.23.5-.5.5s-.5-.22-.5-.5v-7.04l-2 2a.45.45 0 0 1-.57.04h-.02a.4.4 0 0 1-.16-.3.4.4 0 0 1 .1-.32l2.8-2.8a.5.5 0 0 1 .7 0l2.8 2.79a.42.42 0 0 1 .068.498m-.106.138.008.004v-.01zM16 7.063h1.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-11c-1.1 0-2-.9-2-2v-10a2 2 0 0 1 2-2H8a.5.5 0 0 1 .35.15.5.5 0 0 1 .15.35.5.5 0 0 1-.15.35.5.5 0 0 1-.35.15H6.4c-.5 0-.9.4-.9.9v10.2a.9.9 0 0 0 .9.9h11.2c.5 0 .9-.4.9-.9v-10.2c0-.5-.4-.9-.9-.9H16a.5.5 0 0 1 0-1" clip-rule="evenodd"></path></svg></button></div></div></div></div></div></div></div></div></div></footer><div class="sk sl sm sn so l"><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="sp bh r sq"></div><div class="sr l"><div class="ab ss st su ka jz"><div class="sv sw sx sy sz ta tb tc td te ab cp"><div class="h k"><a href="https://medium.com/airbnb-engineering?source=post_page---post_publication_info--a784b2dbe325--------------------------------" rel="noopener follow"><div class="fj ab"><img alt="The Airbnb Tech Blog" class="tf jc jd cx" src="https://miro.medium.com/v2/resize:fill:96:96/1*MlNQKg-sieBGW5prWoe9HQ.jpeg" width="48" height="48" loading="lazy"/><div class="tf l jd jc fs n fr tg"></div></div></a></div><div class="j i d"><a href="https://medium.com/airbnb-engineering?source=post_page---post_publication_info--a784b2dbe325--------------------------------" rel="noopener follow"><div class="fj ab"><img alt="The Airbnb Tech Blog" class="tf ti th cx" src="https://miro.medium.com/v2/resize:fill:128:128/1*MlNQKg-sieBGW5prWoe9HQ.jpeg" width="64" height="64" loading="lazy"/><div class="tf l th ti fs n fr tg"></div></div></a></div><div class="j i d tj jy"><div class="ab"></div></div></div><div class="ab co tk"><div class="tl tm tn to tp l"><a class="af ag ah aj ak al am an ao ap aq ar as at ab q" href="https://medium.com/airbnb-engineering?source=post_page---post_publication_info--a784b2dbe325--------------------------------" rel="noopener follow"><h2 class="pw-author-name bf tr ts tt tu tv tw tx og qb qc ok qe qf oo qh qi bk"><span class="gn tq">Published in <!-- -->The Airbnb Tech Blog</span></h2></a><div class="rp ab jb"><div class="l jy"><span class="pw-follower-count bf b bg z du"><a class="af ag ah ai aj ak al am an ao ap aq ar jr" rel="noopener follow" href="/airbnb-engineering/followers?source=post_page---post_publication_info--a784b2dbe325--------------------------------">148K Followers</a></span></div><div class="bf b bg z du ab ke"><span class="js l" aria-hidden="true"><span class="bf b bg z du">·</span></span><a class="af ag ah ai aj ak al am an ao ap aq ar jr" rel="noopener follow" href="/airbnb-engineering/building-a-user-signals-platform-at-airbnb-b236078ec82b?source=post_page---post_publication_info--a784b2dbe325--------------------------------">Last published <!-- -->3 days ago</a></div></div><div class="ty l"><p class="bf b bg z bk">Creative engineers and data scientists building a world where you can belong anywhere. <a class="af ag ah ai aj ak al am an ao ap aq ar ot go" href="http://airbnb.io" rel="noopener ugc nofollow">http://airbnb.io</a></p></div></div></div><div class="h k"><div class="ab"></div></div></div></div><div class="ab ss st su ka jz"><div class="sv sw sx sy sz ta tb tc td te ab cp"><div class="h k"><a tabindex="0" rel="noopener follow" href="/@sharmila.jesupaul?source=post_page---post_author_info--a784b2dbe325--------------------------------"><div class="l fj"><img alt="Sharmila Jesupaul" class="l fd by jd jc cx" src="https://miro.medium.com/v2/resize:fill:96:96/1*oSKV94STFMJ-V0q6hJPUVA.png" width="48" height="48" loading="lazy"/><div class="fr by l jd jc fs n ay tg"></div></div></a></div><div class="j i d"><a tabindex="0" rel="noopener follow" href="/@sharmila.jesupaul?source=post_page---post_author_info--a784b2dbe325--------------------------------"><div class="l fj"><img alt="Sharmila Jesupaul" class="l fd by th ti cx" src="https://miro.medium.com/v2/resize:fill:128:128/1*oSKV94STFMJ-V0q6hJPUVA.png" width="64" height="64" loading="lazy"/><div class="fr by l th ti fs n ay tg"></div></div></a></div><div class="j i d tj jy"><div class="ab"><span><button class="bf b bg z tz rr ua ub uc ud ue ev ew uf ug uh fa fb fc fd bm fe ff">Follow</button></span></div></div></div><div class="ab co tk"><div class="tl tm tn to tp l"><a class="af ag ah aj ak al am an ao ap aq ar as at ab q" rel="noopener follow" href="/@sharmila.jesupaul?source=post_page---post_author_info--a784b2dbe325--------------------------------"><h2 class="pw-author-name bf tr ts tt tu tv tw tx og qb qc ok qe qf oo qh qi bk"><span class="gn tq">Written by <!-- -->Sharmila Jesupaul</span></h2></a><div class="rp ab jb"><div class="l jy"><span class="pw-follower-count bf b bg z du"><a class="af ag ah ai aj ak al am an ao ap aq ar jr" rel="noopener follow" href="/@sharmila.jesupaul/followers?source=post_page---post_author_info--a784b2dbe325--------------------------------">16 Followers</a></span></div><div class="bf b bg z du ab ke"><span class="js l" aria-hidden="true"><span class="bf b bg z du">·</span></span><a class="af ag ah ai aj ak al am an ao ap aq ar jr" rel="noopener follow" href="/@sharmila.jesupaul/following?source=post_page---post_author_info--a784b2dbe325--------------------------------">1 Following</a></div></div><div class="ty l"></div></div></div><div class="h k"><div class="ab"><span><button class="bf b bg z tz rr ua ub uc ud ue ev ew uf ug uh fa fb fc fd bm fe ff">Follow</button></span></div></div></div></div></div></div><div class="ui uj uk ul um l"><div class="sp bh r ui uj un uo up"></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="ab q cp"><h2 class="bf tr ox ij oz pa im pc pd pf pg ph pj pk pl pn po bk">Responses (<!-- -->4<!-- -->)</h2><div class="ab uq"><div><div class="bm" aria-hidden="false"><a class="ur us" href="https://policy.medium.com/medium-rules-30e5502c4eb4?source=post_page---post_responses--a784b2dbe325--------------------------------" rel="noopener follow" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25"><path fill-rule="evenodd" d="M11.987 5.036a.754.754 0 0 1 .914-.01c.972.721 1.767 1.218 2.6 1.543.828.322 1.719.485 2.887.505a.755.755 0 0 1 .741.757c-.018 3.623-.43 6.256-1.449 8.21-1.034 1.984-2.662 3.209-4.966 4.083a.75.75 0 0 1-.537-.003c-2.243-.874-3.858-2.095-4.897-4.074-1.024-1.951-1.457-4.583-1.476-8.216a.755.755 0 0 1 .741-.757c1.195-.02 2.1-.182 2.923-.503.827-.322 1.6-.815 2.519-1.535m.468.903c-.897.69-1.717 1.21-2.623 1.564-.898.35-1.856.527-3.026.565.037 3.45.469 5.817 1.36 7.515.884 1.684 2.25 2.762 4.284 3.571 2.092-.81 3.465-1.89 4.344-3.575.886-1.698 1.299-4.065 1.334-7.512-1.149-.039-2.091-.217-2.99-.567-.906-.353-1.745-.873-2.683-1.561m-.009 9.155a2.672 2.672 0 1 0 0-5.344 2.672 2.672 0 0 0 0 5.344m0 1a3.672 3.672 0 1 0 0-7.344 3.672 3.672 0 0 0 0 7.344m-1.813-3.777.525-.526.916.917 1.623-1.625.526.526-2.149 2.152z" clip-rule="evenodd"></path></svg></a></div></div></div></div><div class="ut l"><button class="bf b bg z bk rr uu uv uw my mv ue ev ew ex ux uy uz fa va vb vc vd ve fb fc fd bm fe ff">See all responses</button></div></div></div></div><div class="vf vg vh vi vj l bx"><div class="h k j"><div class="sp bh vk vl"></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="vm ab lw kb"><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://help.medium.com/hc/en-us?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Help</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://medium.statuspage.io/?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Status</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/about?autoplay=1&source=post_page-----a784b2dbe325--------------------------------"><p class="bf b dv z du">About</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/jobs-at-medium/work-at-medium-959d1a85284e?source=post_page-----a784b2dbe325--------------------------------"><p class="bf b dv z du">Careers</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="pressinquiries@medium.com?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Press</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://blog.medium.com/?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Blog</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://policy.medium.com/medium-privacy-policy-f03bf92035c9?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Privacy</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://policy.medium.com/medium-terms-of-service-9db0094a1e0f?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Terms</p></a></div><div class="vn vo l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://speechify.com/medium?source=post_page-----a784b2dbe325--------------------------------" rel="noopener follow"><p class="bf b dv z du">Text to speech</p></a></div><div class="vn l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/business?source=post_page-----a784b2dbe325--------------------------------"><p class="bf b dv z du">Teams</p></a></div></div></div></div></div></div></div></div></div></div><script>window.__BUILD_ID__="main-20241122-185319-7bcdc08639"</script><script>window.__GRAPHQL_URI__ = "https://medium.com/_/graphql"</script><script>window.__PRELOADED_STATE__ = {"algolia":{"queries":{}},"cache":{"experimentGroupSet":true,"reason":"","group":"enabled","tags":["group-edgeCachePosts","post-a784b2dbe325","user-1186193d899d","collection-53c7c27702d5"],"serverVariantState":"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","middlewareEnabled":true,"cacheStatus":"DYNAMIC","shouldUseCache":true,"vary":[],"lohpSummerUpsellEnabled":false,"publicationHierarchyEnabledWeb":false,"postBottomResponsesEnabled":false},"client":{"hydrated":false,"isUs":false,"isNativeMedium":false,"isSafariMobile":false,"isSafari":false,"isFirefox":false,"routingEntity":{"type":"DEFAULT","explicit":false},"viewerIsBot":false},"debug":{"requestId":"00415e2b-d9ac-4ab1-8e9e-9d8e49cb6fac","hybridDevServices":[],"originalSpanCarrier":{"traceparent":"00-18fb9c53e1fce8179af4c6de0b7f4c16-ddec81f6b523fa34-01"}},"multiVote":{"clapsPerPost":{}},"navigation":{"branch":{"show":null,"hasRendered":null,"blockedByCTA":false},"hideGoogleOneTap":false,"hasRenderedAlternateUserBanner":null,"currentLocation":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Fadopting-bazel-for-web-at-scale-a784b2dbe325","host":"medium.com","hostname":"medium.com","referrer":"","hasSetReferrer":false,"susiModal":{"step":null,"operation":"register"},"postRead":false,"partnerProgram":{"selectedCountryCode":null},"queryString":"","currentHash":""},"config":{"nodeEnv":"production","version":"main-20241122-185319-7bcdc08639","target":"production","productName":"Medium","publicUrl":"https:\u002F\u002Fcdn-client.medium.com\u002Flite","authDomain":"medium.com","authGoogleClientId":"216296035834-k1k6qe060s2tp2a2jam4ljdcms00sttg.apps.googleusercontent.com","favicon":"production","glyphUrl":"https:\u002F\u002Fglyph.medium.com","branchKey":"key_live_ofxXr2qTrrU9NqURK8ZwEhknBxiI6KBm","algolia":{"appId":"MQ57UUUQZ2","apiKeySearch":"394474ced050e3911ae2249ecc774921","indexPrefix":"medium_","host":"-dsn.algolia.net"},"recaptchaKey":"6Lfc37IUAAAAAKGGtC6rLS13R1Hrw_BqADfS1LRk","recaptcha3Key":"6Lf8R9wUAAAAABMI_85Wb8melS7Zj6ziuf99Yot5","recaptchaEnterpriseKeyId":"6Le-uGgpAAAAAPprRaokM8AKthQ9KNGdoxaGUvVp","datadog":{"applicationId":"6702d87d-a7e0-42fe-bbcb-95b469547ea0","clientToken":"pub853ea8d17ad6821d9f8f11861d23dfed","rumToken":"pubf9cc52896502b9413b68ba36fc0c7162","context":{"deployment":{"target":"production","tag":"main-20241122-185319-7bcdc08639","commit":"7bcdc08639c179dc5172558201a3fd3abc1b5db6"}},"datacenter":"us"},"googleAnalyticsCode":"G-7JY7T788PK","googlePay":{"apiVersion":"2","apiVersionMinor":"0","merchantId":"BCR2DN6TV7EMTGBM","merchantName":"Medium","instanceMerchantId":"13685562959212738550"},"applePay":{"version":3},"signInWallCustomDomainCollectionIds":["3a8144eabfe3","336d898217ee","61061eb0c96b","138adf9c44c","819cc2aaeee0"],"mediumMastodonDomainName":"me.dm","mediumOwnedAndOperatedCollectionIds":["8a9336e5bb4","b7e45b22fec3","193b68bd4fba","8d6b8a439e32","54c98c43354d","3f6ecf56618","d944778ce714","92d2092dc598","ae2a65f35510","1285ba81cada","544c7006046e","fc8964313712","40187e704f1c","88d9857e584e","7b6769f2748b","bcc38c8f6edf","cef6983b292","cb8577c9149e","444d13b52878","713d7dbc99b0","ef8e90590e66","191186aaafa0","55760f21cdc5","9dc80918cc93","bdc4052bbdba","8ccfed20cbb2"],"tierOneDomains":["medium.com","thebolditalic.com","arcdigital.media","towardsdatascience.com","uxdesign.cc","codeburst.io","psiloveyou.xyz","writingcooperative.com","entrepreneurshandbook.co","prototypr.io","betterhumans.coach.me","theascent.pub"],"topicsToFollow":["d61cf867d93f","8a146bc21b28","1eca0103fff3","4d562ee63426","aef1078a3ef5","e15e46793f8d","6158eb913466","55f1c20aba7a","3d18b94f6858","4861fee224fd","63c6f1f93ee","1d98b3a9a871","decb52b64abf","ae5d4995e225","830cded25262"],"topicToTagMappings":{"accessibility":"accessibility","addiction":"addiction","android-development":"android-development","art":"art","artificial-intelligence":"artificial-intelligence","astrology":"astrology","basic-income":"basic-income","beauty":"beauty","biotech":"biotech","blockchain":"blockchain","books":"books","business":"business","cannabis":"cannabis","cities":"cities","climate-change":"climate-change","comics":"comics","coronavirus":"coronavirus","creativity":"creativity","cryptocurrency":"cryptocurrency","culture":"culture","cybersecurity":"cybersecurity","data-science":"data-science","design":"design","digital-life":"digital-life","disability":"disability","economy":"economy","education":"education","equality":"equality","family":"family","feminism":"feminism","fiction":"fiction","film":"film","fitness":"fitness","food":"food","freelancing":"freelancing","future":"future","gadgets":"gadgets","gaming":"gaming","gun-control":"gun-control","health":"health","history":"history","humor":"humor","immigration":"immigration","ios-development":"ios-development","javascript":"javascript","justice":"justice","language":"language","leadership":"leadership","lgbtqia":"lgbtqia","lifestyle":"lifestyle","machine-learning":"machine-learning","makers":"makers","marketing":"marketing","math":"math","media":"media","mental-health":"mental-health","mindfulness":"mindfulness","money":"money","music":"music","neuroscience":"neuroscience","nonfiction":"nonfiction","outdoors":"outdoors","parenting":"parenting","pets":"pets","philosophy":"philosophy","photography":"photography","podcasts":"podcast","poetry":"poetry","politics":"politics","privacy":"privacy","product-management":"product-management","productivity":"productivity","programming":"programming","psychedelics":"psychedelics","psychology":"psychology","race":"race","relationships":"relationships","religion":"religion","remote-work":"remote-work","san-francisco":"san-francisco","science":"science","self":"self","self-driving-cars":"self-driving-cars","sexuality":"sexuality","social-media":"social-media","society":"society","software-engineering":"software-engineering","space":"space","spirituality":"spirituality","sports":"sports","startups":"startup","style":"style","technology":"technology","transportation":"transportation","travel":"travel","true-crime":"true-crime","tv":"tv","ux":"ux","venture-capital":"venture-capital","visual-design":"visual-design","work":"work","world":"world","writing":"writing"},"defaultImages":{"avatar":{"imageId":"1*dmbNkD5D-u45r44go_cf0g.png","height":150,"width":150},"orgLogo":{"imageId":"7*V1_7XP4snlmqrc_0Njontw.png","height":110,"width":500},"postLogo":{"imageId":"bd978bb536350a710e8efb012513429cabdc4c28700604261aeda246d0f980b7","height":810,"width":1440},"postPreviewImage":{"imageId":"1*hn4v1tCaJy7cWMyb0bpNpQ.png","height":386,"width":579}},"collectionStructuredData":{"8d6b8a439e32":{"name":"Elemental","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F980\u002F1*9ygdqoKprhwuTVKUM0DLPA@2x.png","width":980,"height":159}}},"3f6ecf56618":{"name":"Forge","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F596\u002F1*uULpIlImcO5TDuBZ6lm7Lg@2x.png","width":596,"height":183}}},"ae2a65f35510":{"name":"GEN","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F264\u002F1*RdVZMdvfV3YiZTw6mX7yWA.png","width":264,"height":140}}},"88d9857e584e":{"name":"LEVEL","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*JqYMhNX6KNNb2UlqGqO2WQ.png","width":540,"height":108}}},"7b6769f2748b":{"name":"Marker","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F383\u002F1*haCUs0wF6TgOOvfoY-jEoQ@2x.png","width":383,"height":92}}},"444d13b52878":{"name":"OneZero","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*cw32fIqCbRWzwJaoQw6BUg.png","width":540,"height":123}}},"8ccfed20cbb2":{"name":"Zora","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*tZUQqRcCCZDXjjiZ4bDvgQ.png","width":540,"height":106}}}},"embeddedPostIds":{"coronavirus":"cd3010f9d81f"},"sharedCdcMessaging":{"COVID_APPLICABLE_TAG_SLUGS":[],"COVID_APPLICABLE_TOPIC_NAMES":[],"COVID_APPLICABLE_TOPIC_NAMES_FOR_TOPIC_PAGE":[],"COVID_MESSAGES":{"tierA":{"text":"For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":66,"end":73,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"tierB":{"text":"Anyone can publish on Medium per our Policies, but we don’t fact-check every story. For more info about the coronavirus, see cdc.gov.","markups":[{"start":37,"end":45,"href":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Fcategories\u002F201931128-Policies-Safety"},{"start":125,"end":132,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"paywall":{"text":"This article has been made free for everyone, thanks to Medium Members. For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":56,"end":70,"href":"https:\u002F\u002Fmedium.com\u002Fmembership"},{"start":138,"end":145,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"unbound":{"text":"This article is free for everyone, thanks to Medium Members. For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":45,"end":59,"href":"https:\u002F\u002Fmedium.com\u002Fmembership"},{"start":127,"end":134,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]}},"COVID_BANNER_POST_ID_OVERRIDE_WHITELIST":["3b31a67bff4a"]},"sharedVoteMessaging":{"TAGS":["politics","election-2020","government","us-politics","election","2020-presidential-race","trump","donald-trump","democrats","republicans","congress","republican-party","democratic-party","biden","joe-biden","maga"],"TOPICS":["politics","election"],"MESSAGE":{"text":"Find out more about the U.S. election results here.","markups":[{"start":46,"end":50,"href":"https:\u002F\u002Fcookpolitical.com\u002F2020-national-popular-vote-tracker"}]},"EXCLUDE_POSTS":["397ef29e3ca5"]},"embedPostRules":[],"recircOptions":{"v1":{"limit":3},"v2":{"limit":8}},"braintreeClientKey":"production_zjkj96jm_m56f8fqpf7ngnrd4","braintree":{"enabled":true,"merchantId":"m56f8fqpf7ngnrd4","merchantAccountId":{"usd":"AMediumCorporation_instant","eur":"amediumcorporation_EUR","cad":"amediumcorporation_CAD"},"publicKey":"ds2nn34bg2z7j5gd","braintreeEnvironment":"production","dashboardUrl":"https:\u002F\u002Fwww.braintreegateway.com\u002Fmerchants","gracePeriodDurationInDays":14,"mediumMembershipPlanId":{"monthly":"ce105f8c57a3","monthlyV2":"e8a5e126-792b-4ee6-8fba-d574c1b02fc5","monthlyWithTrial":"d5ee3dbe3db8","monthlyPremium":"fa741a9b47a2","yearly":"a40ad4a43185","yearlyV2":"3815d7d6-b8ca-4224-9b8c-182f9047866e","yearlyStaff":"d74fb811198a","yearlyWithTrial":"b3bc7350e5c7","yearlyPremium":"e21bd2c12166","monthlyOneYearFree":"e6c0637a-2bad-4171-ab4f-3c268633d83c","monthly25PercentOffFirstYear":"235ecc62-0cdb-49ae-9378-726cd21c504b","monthly20PercentOffFirstYear":"ba518864-9c13-4a99-91ca-411bf0cac756","monthly15PercentOffFirstYear":"594c029b-9f89-43d5-88f8-8173af4e070e","monthly10PercentOffFirstYear":"c6c7bc9a-40f2-4b51-8126-e28511d5bdb0","monthlyForStudents":"629ebe51-da7d-41fd-8293-34cd2f2030a8","yearlyOneYearFree":"78ba7be9-0d9f-4ece-aa3e-b54b826f2bf1","yearly25PercentOffFirstYear":"2dbb010d-bb8f-4eeb-ad5c-a08509f42d34","yearly20PercentOffFirstYear":"47565488-435b-47f8-bf93-40d5fbe0ebc8","yearly15PercentOffFirstYear":"8259809b-0881-47d9-acf7-6c001c7f720f","yearly10PercentOffFirstYear":"9dd694fb-96e1-472c-8d9e-3c868d5c1506","yearlyForStudents":"e29345ef-ab1c-4234-95c5-70e50fe6bc23","monthlyCad":"p52orjkaceei","yearlyCad":"h4q9g2up9ktt"},"braintreeDiscountId":{"oneMonthFree":"MONTHS_FREE_01","threeMonthsFree":"MONTHS_FREE_03","sixMonthsFree":"MONTHS_FREE_06","fiftyPercentOffOneYear":"FIFTY_PERCENT_OFF_ONE_YEAR"},"3DSecureVersion":"2","defaultCurrency":"usd","providerPlanIdCurrency":{"4ycw":"usd","rz3b":"usd","3kqm":"usd","jzw6":"usd","c2q2":"usd","nnsw":"usd","q8qw":"usd","d9y6":"usd","fx7w":"cad","nwf2":"cad"}},"paypalClientId":"AXj1G4fotC2GE8KzWX9mSxCH1wmPE3nJglf4Z2ig_amnhvlMVX87otaq58niAg9iuLktVNF_1WCMnN7v","paypal":{"host":"https:\u002F\u002Fapi.paypal.com:443","clientMode":"production","serverMode":"live","webhookId":"4G466076A0294510S","monthlyPlan":{"planId":"P-9WR0658853113943TMU5FDQA","name":"Medium Membership (Monthly) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"yearlyPlan":{"planId":"P-7N8963881P8875835MU5JOPQ","name":"Medium Membership (Annual) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"oneYearGift":{"name":"Medium Membership (1 Year, Digital Gift Code)","description":"Unlimited access to the best and brightest stories on Medium. Gift codes can be redeemed at medium.com\u002Fredeem.","price":"50.00","currency":"USD","sku":"membership-gift-1-yr"},"oldMonthlyPlan":{"planId":"P-96U02458LM656772MJZUVH2Y","name":"Medium Membership (Monthly)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"oldYearlyPlan":{"planId":"P-59P80963JF186412JJZU3SMI","name":"Medium Membership (Annual)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"monthlyPlanWithTrial":{"planId":"P-66C21969LR178604GJPVKUKY","name":"Medium Membership (Monthly) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"yearlyPlanWithTrial":{"planId":"P-6XW32684EX226940VKCT2MFA","name":"Medium Membership (Annual) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"oldMonthlyPlanNoSetupFee":{"planId":"P-4N046520HR188054PCJC7LJI","name":"Medium Membership (Monthly)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"oldYearlyPlanNoSetupFee":{"planId":"P-7A4913502Y5181304CJEJMXQ","name":"Medium Membership (Annual)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"sdkUrl":"https:\u002F\u002Fwww.paypal.com\u002Fsdk\u002Fjs"},"stripePublishableKey":"pk_live_7FReX44VnNIInZwrIIx6ghjl","log":{"json":true,"level":"info"},"imageUploadMaxSizeMb":25,"staffPicks":{"title":"Staff Picks","catalogId":"c7bc6e1ee00f"}},"session":{"xsrf":""}}</script><script>window.__APOLLO_STATE__ = {"ROOT_QUERY":{"__typename":"Query","viewer":null,"collectionByDomainOrSlug({\"domainOrSlug\":\"airbnb-engineering\"})":{"__ref":"Collection:53c7c27702d5"},"postResult({\"id\":\"a784b2dbe325\"})":{"__ref":"Post:a784b2dbe325"}},"Collection:53c7c27702d5":{"__typename":"Collection","id":"53c7c27702d5","customStyleSheet":null,"colorPalette":{"__typename":"ColorPalette","highlightSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FFFFFFFF","colorPoints":[{"__typename":"ColorPoint","color":"#FFE4F7F8","point":0},{"__typename":"ColorPoint","color":"#FFDFF6F7","point":0.1},{"__typename":"ColorPoint","color":"#FFDAF6F6","point":0.2},{"__typename":"ColorPoint","color":"#FFD5F5F6","point":0.3},{"__typename":"ColorPoint","color":"#FFCFF4F5","point":0.4},{"__typename":"ColorPoint","color":"#FFC9F3F4","point":0.5},{"__typename":"ColorPoint","color":"#FFC3F2F4","point":0.6},{"__typename":"ColorPoint","color":"#FFBDF1F3","point":0.7},{"__typename":"ColorPoint","color":"#FFB7F0F3","point":0.8},{"__typename":"ColorPoint","color":"#FFB1F0F2","point":0.9},{"__typename":"ColorPoint","color":"#FFAAEFF1","point":1}]},"defaultBackgroundSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FFFFFFFF","colorPoints":[{"__typename":"ColorPoint","color":"#FF30969A","point":0},{"__typename":"ColorPoint","color":"#FF338B8E","point":0.1},{"__typename":"ColorPoint","color":"#FF338083","point":0.2},{"__typename":"ColorPoint","color":"#FF337477","point":0.3},{"__typename":"ColorPoint","color":"#FF31696B","point":0.4},{"__typename":"ColorPoint","color":"#FF2F5D5F","point":0.5},{"__typename":"ColorPoint","color":"#FF2B5153","point":0.6},{"__typename":"ColorPoint","color":"#FF264546","point":0.7},{"__typename":"ColorPoint","color":"#FF203839","point":0.8},{"__typename":"ColorPoint","color":"#FF192B2C","point":0.9},{"__typename":"ColorPoint","color":"#FF101D1D","point":1}]},"tintBackgroundSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FF007E82","colorPoints":[{"__typename":"ColorPoint","color":"#FF007E82","point":0},{"__typename":"ColorPoint","color":"#FF368D91","point":0.1},{"__typename":"ColorPoint","color":"#FF529C9F","point":0.2},{"__typename":"ColorPoint","color":"#FF6AAAAD","point":0.3},{"__typename":"ColorPoint","color":"#FF80B8BA","point":0.4},{"__typename":"ColorPoint","color":"#FF95C5C7","point":0.5},{"__typename":"ColorPoint","color":"#FFA9D2D4","point":0.6},{"__typename":"ColorPoint","color":"#FFBDDFE0","point":0.7},{"__typename":"ColorPoint","color":"#FFCFEBEC","point":0.8},{"__typename":"ColorPoint","color":"#FFE2F7F7","point":0.9},{"__typename":"ColorPoint","color":"#FFF4FFFF","point":1}]}},"domain":null,"slug":"airbnb-engineering","googleAnalyticsId":null,"editors":[{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:ebe93072cafd"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:715069fb9693"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:2c64fccbad80"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:f4c9b6905436"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:d06d84a4dee7"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:ae9de0d76057"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:8a8dba98ccda"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:ba68c6ee8dae"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:5315ce63140f"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:23561a2a5df3"}}],"name":"The Airbnb Tech Blog","avatar":{"__ref":"ImageMetadata:1*MlNQKg-sieBGW5prWoe9HQ.jpeg"},"description":"Creative engineers and data scientists building a world where you can belong anywhere. http:\u002F\u002Fairbnb.io","subscriberCount":148668,"latestPostsConnection({\"paging\":{\"limit\":1}})":{"__typename":"PostConnection","posts":[{"__ref":"Post:b236078ec82b"}]},"viewerEdge":{"__ref":"CollectionViewerEdge:collectionId:53c7c27702d5-viewerId:lo_6d67835949fb"},"twitterUsername":"AirbnbEng","facebookPageId":null,"logo":{"__ref":"ImageMetadata:1*JZl-TXoSiG0VmYn3qWLdTA.png"},"favicon":{"__ref":"ImageMetadata:"}},"User:ebe93072cafd":{"__typename":"User","id":"ebe93072cafd"},"User:715069fb9693":{"__typename":"User","id":"715069fb9693"},"User:2c64fccbad80":{"__typename":"User","id":"2c64fccbad80"},"User:f4c9b6905436":{"__typename":"User","id":"f4c9b6905436"},"User:d06d84a4dee7":{"__typename":"User","id":"d06d84a4dee7"},"User:ae9de0d76057":{"__typename":"User","id":"ae9de0d76057"},"User:8a8dba98ccda":{"__typename":"User","id":"8a8dba98ccda"},"User:ba68c6ee8dae":{"__typename":"User","id":"ba68c6ee8dae"},"User:5315ce63140f":{"__typename":"User","id":"5315ce63140f"},"User:23561a2a5df3":{"__typename":"User","id":"23561a2a5df3"},"ImageMetadata:1*MlNQKg-sieBGW5prWoe9HQ.jpeg":{"__typename":"ImageMetadata","id":"1*MlNQKg-sieBGW5prWoe9HQ.jpeg"},"User:ffe99664019a":{"__typename":"User","id":"ffe99664019a","customDomainState":null,"hasSubdomain":false,"username":"kidaikwon36"},"Post:b236078ec82b":{"__typename":"Post","id":"b236078ec82b","firstPublishedAt":1732130847368,"creator":{"__ref":"User:ffe99664019a"},"collection":{"__ref":"Collection:53c7c27702d5"},"isSeries":false,"mediumUrl":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Fbuilding-a-user-signals-platform-at-airbnb-b236078ec82b","sequence":null,"uniqueSlug":"building-a-user-signals-platform-at-airbnb-b236078ec82b"},"LinkedAccounts:1186193d899d":{"__typename":"LinkedAccounts","mastodon":null,"id":"1186193d899d"},"UserViewerEdge:userId:1186193d899d-viewerId:lo_6d67835949fb":{"__typename":"UserViewerEdge","id":"userId:1186193d899d-viewerId:lo_6d67835949fb","isFollowing":false,"isUser":false,"isMuting":false},"User:1186193d899d":{"__typename":"User","id":"1186193d899d","linkedAccounts":{"__ref":"LinkedAccounts:1186193d899d"},"isSuspended":false,"imageId":"1*oSKV94STFMJ-V0q6hJPUVA.png","mediumMemberAt":0,"verifications":{"__typename":"VerifiedInfo","isBookAuthor":false},"name":"Sharmila Jesupaul","socialStats":{"__typename":"SocialStats","followerCount":16,"followingCount":1,"collectionFollowingCount":0},"username":"sharmila.jesupaul","customDomainState":null,"hasSubdomain":false,"bio":"","isPartnerProgramEnrolled":false,"viewerEdge":{"__ref":"UserViewerEdge:userId:1186193d899d-viewerId:lo_6d67835949fb"},"viewerIsUser":false,"newsletterV3":null,"postSubscribeMembershipUpsellShownAt":0,"membership":null,"allowNotes":true,"twitterScreenName":""},"ImageMetadata:1*uMA-yyBcSyRjQBwdQnbDdw.jpeg":{"__typename":"ImageMetadata","id":"1*uMA-yyBcSyRjQBwdQnbDdw.jpeg","originalHeight":960,"originalWidth":1440,"focusPercentX":null,"focusPercentY":null,"alt":"A person making pesto sauce with a mortar and pestle"},"Paragraph:540c54e0e4f6_0":{"__typename":"Paragraph","id":"540c54e0e4f6_0","name":"ad32","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*uMA-yyBcSyRjQBwdQnbDdw.jpeg"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_1":{"__typename":"Paragraph","id":"540c54e0e4f6_1","name":"cde1","type":"H3","href":null,"layout":null,"metadata":null,"text":"Adopting Bazel for Web at Scale","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_2":{"__typename":"Paragraph","id":"540c54e0e4f6_2","name":"dfaa","type":"H4","href":null,"layout":null,"metadata":null,"text":"How and Why We Migrated Airbnb’s Large-Scale Web Monorepo to Bazel","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_3":{"__typename":"Paragraph","id":"540c54e0e4f6_3","name":"3dee","type":"P","href":null,"layout":null,"metadata":null,"text":"By: Brie Bunge and Sharmila Jesupaul","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":4,"end":14,"href":"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbbunge\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":19,"end":36,"href":"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsharmilajesupaul\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":0,"end":3,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_4":{"__typename":"Paragraph","id":"540c54e0e4f6_4","name":"227d","type":"H3","href":null,"layout":null,"metadata":null,"text":"Introduction","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_5":{"__typename":"Paragraph","id":"540c54e0e4f6_5","name":"ab69","type":"P","href":null,"layout":null,"metadata":null,"text":"At Airbnb, we’ve recently adopted Bazel — Google’s open source build tool–as our universal build system across backend, web, and iOS platforms. This post will cover our experience adopting Bazel for Airbnb’s large-scale (over 11 million lines of code) web monorepo. We’ll share how we prepared the code base, the principles that guided the migration, and the process of migrating selected CI jobs. Our goal is to share information that would have been valuable to us when we embarked on this journey and to contribute to the growing discussion around Bazel for web development.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":34,"end":39,"href":"https:\u002F\u002Fbazel.build\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":129,"end":132,"href":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Fmigrating-our-ios-build-system-from-buck-to-bazel-ddd6f3f25aa3","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_6":{"__typename":"Paragraph","id":"540c54e0e4f6_6","name":"698d","type":"H3","href":null,"layout":null,"metadata":null,"text":"Why did we do this?","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_7":{"__typename":"Paragraph","id":"540c54e0e4f6_7","name":"eb0e","type":"P","href":null,"layout":null,"metadata":null,"text":"Historically, we wrote bespoke build scripts and caching logic for various continuous integration (CI) jobs that proved challenging to maintain and consistently reached scaling limits as the repo grew. For example, our linter, ESLint, and TypeScript’s type checking did not support multi-threaded concurrency out-of-the-box. We extended our unit testing tool, Jest, to be the runner for these tools because it had an API to leverage multiple workers.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":227,"end":233,"href":"https:\u002F\u002Feslint.org\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":360,"end":364,"href":"https:\u002F\u002Fjestjs.io\u002F","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_8":{"__typename":"Paragraph","id":"540c54e0e4f6_8","name":"9ec5","type":"P","href":null,"layout":null,"metadata":null,"text":"It was not sustainable to continually create workarounds to overcome the inefficiencies of our tooling which did not support concurrency and we were incurring a long-run maintenance cost. To tackle these challenges and to best support our growing codebase, we found that Bazel’s sophistication, parallelism, caching, and performance fulfilled our needs.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_9":{"__typename":"Paragraph","id":"540c54e0e4f6_9","name":"c7ba","type":"P","href":null,"layout":null,"metadata":null,"text":"Additionally, Bazel is language agnostic. This facilitated consolidation onto a single, universal build system across Airbnb and allowed us to share common infrastructure and expertise. Now, an engineer who works on our backend monorepo can switch to the web monorepo and know how to build and test things.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_10":{"__typename":"Paragraph","id":"540c54e0e4f6_10","name":"7c6e","type":"H3","href":null,"layout":null,"metadata":null,"text":"Why was this hard?","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_11":{"__typename":"Paragraph","id":"540c54e0e4f6_11","name":"2081","type":"P","href":null,"layout":null,"metadata":null,"text":"When we began the migration in 2021, there was no publicized industry precedent for integrating Bazel with web at scale outside of Google. Open source tooling didn’t work out-of-the-box, and leveraging remote build execution (RBE) introduced additional challenges. Our web codebase is large and contains many loose files, which led to performance issues when transmitting them to the remote environment. Additionally, we established migration principles that included improving or maintaining overall performance and reducing the impact on developers contributing to the monorepo during the transition. We effectively achieved both of these goals. Read on for more details.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":202,"end":224,"href":"https:\u002F\u002Fbazel.build\u002Fremote\u002Frbe","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_12":{"__typename":"Paragraph","id":"540c54e0e4f6_12","name":"f50f","type":"H3","href":null,"layout":null,"metadata":null,"text":"Readying the Repository","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_13":{"__typename":"Paragraph","id":"540c54e0e4f6_13","name":"8d95","type":"P","href":null,"layout":null,"metadata":null,"text":"We did some work up front to make the repository Bazel-ready–namely, cycle breaking and automated BUILD.bazel file generation.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_14":{"__typename":"Paragraph","id":"540c54e0e4f6_14","name":"b949","type":"H4","href":null,"layout":null,"metadata":null,"text":"Cycle Breaking","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_15":{"__typename":"Paragraph","id":"540c54e0e4f6_15","name":"18d1","type":"P","href":null,"layout":null,"metadata":null,"text":"Our monorepo is laid out with projects under a top-level frontend\u002F directory. To start, we wanted to add BUILD.bazel files to each of the ~1000 top-level frontend directories. However, doing so created cycles in the dependency graph. This is not allowed in Bazel because there needs to be a DAG of build targets. Breaking these often felt like battling a hydra, as removing one cycle spawns more in its place. To accelerate the process, we modeled the problem as finding the minimum feedback arc set (MFAS)¹ to identify the minimal set of edges to remove leaving a DAG. This set presented the least disruption, level of effort, and surfaced pathological edges.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":291,"end":294,"href":"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDirected_acyclic_graph","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":475,"end":506,"href":"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFeedback_arc_set","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":565,"end":568,"href":"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDirected_acyclic_graph","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_16":{"__typename":"Paragraph","id":"540c54e0e4f6_16","name":"6725","type":"H4","href":null,"layout":null,"metadata":null,"text":"Automated BUILD.bazel Generation","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_17":{"__typename":"Paragraph","id":"540c54e0e4f6_17","name":"fd5c","type":"P","href":null,"layout":null,"metadata":null,"text":"We automatically generate BUILD.bazel files for the following reasons:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_18":{"__typename":"Paragraph","id":"540c54e0e4f6_18","name":"80c7","type":"OLI","href":null,"layout":null,"metadata":null,"text":"Most contents are knowable from statically analyzable import \u002F require statements.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_19":{"__typename":"Paragraph","id":"540c54e0e4f6_19","name":"915f","type":"OLI","href":null,"layout":null,"metadata":null,"text":"Automation allowed us to quickly iterate on BUILD.bazel changes as we refined our rule definitions.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_20":{"__typename":"Paragraph","id":"540c54e0e4f6_20","name":"bc18","type":"OLI","href":null,"layout":null,"metadata":null,"text":"It would take time for the migration to complete and we didn’t want to ask users to keep these files up-to-date when they weren’t yet gaining value from them.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_21":{"__typename":"Paragraph","id":"540c54e0e4f6_21","name":"104c","type":"OLI","href":null,"layout":null,"metadata":null,"text":"Manually keeping these files up-to-date would constitute an additional Bazel tax, regressing the developer experience.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_22":{"__typename":"Paragraph","id":"540c54e0e4f6_22","name":"8ed0","type":"P","href":null,"layout":null,"metadata":null,"text":"We have a CLI tool called sync-configs that generates dependency-based configurations in the monorepo (e.g., tsconfig.json, project configuration, now BUILD.bazel). It uses jest-haste-map and watchman with a custom version of the dependencyExtractor to determine the file-level dependency graph and part of Gazelle to emit BUILD.bazel files. This CLI tool is similar to Gazelle but also generates additional web specific configuration files such as tsconfig.json files used in TypeScript compilation.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":173,"end":187,"href":"https:\u002F\u002Fgithub.com\u002Fjestjs\u002Fjest\u002Ftree\u002Fmain\u002Fpackages\u002Fjest-haste-map","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":192,"end":200,"href":"https:\u002F\u002Ffacebook.github.io\u002Fwatchman\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":230,"end":249,"href":"https:\u002F\u002Fgithub.com\u002Fjestjs\u002Fjest\u002Fblob\u002Fmain\u002Fpackages\u002Fjest-haste-map\u002Fsrc\u002Flib\u002FdependencyExtractor.ts","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":307,"end":314,"href":"https:\u002F\u002Fgithub.com\u002Fbazelbuild\u002Fbazel-gazelle","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":370,"end":377,"href":"https:\u002F\u002Fgithub.com\u002Fbazelbuild\u002Fbazel-gazelle","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_23":{"__typename":"Paragraph","id":"540c54e0e4f6_23","name":"234e","type":"H3","href":null,"layout":null,"metadata":null,"text":"CI Migration","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_24":{"__typename":"Paragraph","id":"540c54e0e4f6_24","name":"5b70","type":"P","href":null,"layout":null,"metadata":null,"text":"With preparation work complete, we proceeded to migrate CI jobs to Bazel. This was a massive undertaking, so we divided the work into incremental milestones. We audited our CI jobs and chose to migrate the ones that would benefit the most: type checking, linting, and unit testing². To reduce the burden on our developers, we assigned the central Web Platform team the responsibility for porting CI jobs to Bazel. We proceeded one job at a time to deliver incremental value to developers sooner, gain confidence in our approach, focus our efforts, and build momentum. With each job, we ensured that the developer experience was high-quality, that performance improved, CI failures were reproducible locally, and that the tooling Bazel replaced was fully deprecated and removed.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_25":{"__typename":"Paragraph","id":"540c54e0e4f6_25","name":"3986","type":"H3","href":null,"layout":null,"metadata":null,"text":"Enabling TypeScript","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_26":{"__typename":"Paragraph","id":"540c54e0e4f6_26","name":"a5df","type":"P","href":null,"layout":null,"metadata":null,"text":"We started with the TypeScript (TS) CI job. We first tried the open source ts_project rule³. However, it didn’t work well with RBE due to the sheer number of inputs, so we wrote a custom rule to reduce the number and size of the inputs.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":75,"end":90,"href":"https:\u002F\u002Fgithub.com\u002Fbazelbuild\u002Frules_nodejs\u002Fblob\u002F5.x\u002Fnodejs\u002Fprivate\u002Fts_project.bzl","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":180,"end":191,"href":"https:\u002F\u002Fgist.github.com\u002Fbrieb\u002F8439c7869fa058554c58377fb52a3c84","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_27":{"__typename":"Paragraph","id":"540c54e0e4f6_27","name":"6ae1","type":"P","href":null,"layout":null,"metadata":null,"text":"The biggest source of inputs came from node_modules. Prior to this, the files for each npm package were being uploaded individually. Since Bazel works well with Java, we packaged up a full tar and a TS-specific tar (only containing the *.ts and package.json) for each npm package along the lines of Java JAR files (essentially zips).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":39,"end":51,"href":"https:\u002F\u002Fa0.muscache.com\u002Fim\u002Fpictures\u002Fairbnb-platform-assets\u002FAirbnbPlatformAssets-Bazel%20blogpost\u002Foriginal\u002F6826576e-79dc-4382-bc37-a62d9be3f597.png","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_28":{"__typename":"Paragraph","id":"540c54e0e4f6_28","name":"5f2f","type":"P","href":null,"layout":null,"metadata":null,"text":"Another source of inputs came through transitive dependencies. Transitive node_modules and d.ts files in the sandbox were being included because technically they can be needed for subsequent project compilations. For example, suppose project foo depends on bar, and types from bar are exposed in foo’s emit. As a result, project baz which depends on foo would also need bar’s outputs in the sandbox. For long chains of dependencies, this can bloat the inputs significantly with files that aren’t actually needed. TypeScript has a — listFiles flag that tells us which files are part of the compilation. We can package up this limited set of files along with the emitted d.ts files into an output tsc.tar.gz file⁴. With this, targets need only include direct dependencies, rather than all transitive dependencies⁵.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":530,"end":546,"href":"https:\u002F\u002Fwww.typescriptlang.org\u002Ftsconfig\u002F#listFiles","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*cKjuiMnl5KNyCRgG":{"__typename":"ImageMetadata","id":"0*cKjuiMnl5KNyCRgG","originalHeight":702,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:540c54e0e4f6_29":{"__typename":"Paragraph","id":"540c54e0e4f6_29","name":"dd1b","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*cKjuiMnl5KNyCRgG"},"text":"Diagram showing how we use tars and the — listFiles flag to prune inputs\u002Foutputs of :types targets","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":98,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_30":{"__typename":"Paragraph","id":"540c54e0e4f6_30","name":"e8de","type":"P","href":null,"layout":null,"metadata":null,"text":"This custom rule unblocked switching to Bazel for TypeScript, as the job was now well under our CI runtime budget.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*BJB0TroGRohVvjAS":{"__typename":"ImageMetadata","id":"0*BJB0TroGRohVvjAS","originalHeight":511,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:540c54e0e4f6_31":{"__typename":"Paragraph","id":"540c54e0e4f6_31","name":"9d1a","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*BJB0TroGRohVvjAS"},"text":"Bar chart showing the speed up from switching to using our custom genrule","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":73,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_32":{"__typename":"Paragraph","id":"540c54e0e4f6_32","name":"74c1","type":"H3","href":null,"layout":null,"metadata":null,"text":"Enabling ESLint","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_33":{"__typename":"Paragraph","id":"540c54e0e4f6_33","name":"56cc","type":"P","href":null,"layout":null,"metadata":null,"text":"We migrated the ESLint job next. Bazel works best with actions that are independent and have a narrow set of inputs. Some of our lint rules (e.g., special internal rules, import\u002Fexport, import\u002Fextensions) inspected files outside of the linted file. We restricted our lint rules to those that could operate in isolation as a way of reducing input size and having only to lint directly affected files. This meant moving or deleting lint rules (e.g., those that were made redundant with TypeScript). As a result, we reduced CI times by over 70%.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":16,"end":22,"href":"https:\u002F\u002Feslint.org\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":171,"end":184,"href":"https:\u002F\u002Fgithub.com\u002Fimport-js\u002Feslint-plugin-import\u002Fblob\u002Fmain\u002Fdocs\u002Frules\u002Fexport.md","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":186,"end":203,"href":"https:\u002F\u002Fgithub.com\u002Fimport-js\u002Feslint-plugin-import\u002Fblob\u002Fmain\u002Fdocs\u002Frules\u002Fextensions.md","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*jxLe6RvjzyINaahq":{"__typename":"ImageMetadata","id":"0*jxLe6RvjzyINaahq","originalHeight":528,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:540c54e0e4f6_34":{"__typename":"Paragraph","id":"540c54e0e4f6_34","name":"b916","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*jxLe6RvjzyINaahq"},"text":"Time series graph showing the runtime speed-up in early May from only running ESLint on directly affected targets","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":113,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_35":{"__typename":"Paragraph","id":"540c54e0e4f6_35","name":"c4df","type":"H3","href":null,"layout":null,"metadata":null,"text":"Enabling Jest","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_36":{"__typename":"Paragraph","id":"540c54e0e4f6_36","name":"5a30","type":"P","href":null,"layout":null,"metadata":null,"text":"Our next challenge was enabling Jest. This presented unique challenges, as we needed to bring along a much larger set of first and third-party dependencies, and there were more Bazel-specific failures to fix.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":32,"end":36,"href":"https:\u002F\u002Fjestjs.io","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_37":{"__typename":"Paragraph","id":"540c54e0e4f6_37","name":"59b6","type":"H4","href":null,"layout":null,"metadata":null,"text":"Worker and Docker Cache","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_38":{"__typename":"Paragraph","id":"540c54e0e4f6_38","name":"6263","type":"P","href":null,"layout":null,"metadata":null,"text":"We tarred up dependencies to reduce input size, but extraction was still slow. To address this, we introduced caching. One layer of cache is on the remote worker and another is on the worker’s Docker container, baked into the image at build time. The Docker layer exists to avoid losing our cache when remote workers are auto-scaled. We run a cron job once a week to update the Docker image with the newest set of cached dependencies, striking a balance of keeping them fresh while avoiding image thrashing. For more details, check out this Bazel Community Day talk.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":536,"end":565,"href":"https:\u002F\u002Fblog.engflow.com\u002F2023\u002F06\u002F01\u002Fbazel-community-day--san-francisco\u002F#taming-node_modules-in-rbe-airbnbs-journey-sharmila-jesupaul-airbnb","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*e7zywi2UKNMa9qTF":{"__typename":"ImageMetadata","id":"0*e7zywi2UKNMa9qTF","originalHeight":903,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:540c54e0e4f6_39":{"__typename":"Paragraph","id":"540c54e0e4f6_39","name":"a11c","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*e7zywi2UKNMa9qTF"},"text":"Diagram showing symlinked npm dependencies to a Docker cache and worker cache","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":77,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_40":{"__typename":"Paragraph","id":"540c54e0e4f6_40","name":"dd3c","type":"P","href":null,"layout":null,"metadata":null,"text":"This added caching provided us with a ~25% speed up of our Jest unit testing CI job overall and reduced the time to extract our dependencies from 1–3 minutes to 3–7 seconds per target. This implementation required us to enable the NodeJS preserve-symlinks option and patch some of our tools that followed symlinks to their real paths. We extended this caching strategy to our Babel transformation cache, another source of poor performance.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":238,"end":255,"href":"https:\u002F\u002Fnodejs.org\u002Fapi\u002Fcli.html#--preserve-symlinks","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":376,"end":381,"href":"https:\u002F\u002Fgithub.com\u002Fjestjs\u002Fjest\u002Ftree\u002Fmain\u002Fpackages\u002Fbabel-jest","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_41":{"__typename":"Paragraph","id":"540c54e0e4f6_41","name":"d47d","type":"H4","href":null,"layout":null,"metadata":null,"text":"Implicit Dependencies","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_42":{"__typename":"Paragraph","id":"540c54e0e4f6_42","name":"d5ff","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, we needed to fix Bazel-specific test failures. Most of these were due to missing files. For any inputs not statically analyzable (e.g., referenced as a string without an import, babel plugin string referenced in .babelrc), we added support for a Bazel keep comment (e.g., \u002F\u002F bazelKeep: path\u002Fto\u002Ffile) which acts as though the file were imported. The advantages of this approach are:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_43":{"__typename":"Paragraph","id":"540c54e0e4f6_43","name":"e5ba","type":"P","href":null,"layout":null,"metadata":null,"text":"1. It is colocated with the code that uses the dependency,","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_44":{"__typename":"Paragraph","id":"540c54e0e4f6_44","name":"55e0","type":"P","href":null,"layout":null,"metadata":null,"text":"2. BUILD.bazel files don’t need to be manually edited to add\u002Fmove # keep comments,","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":66,"end":81,"href":"https:\u002F\u002Fgithub.com\u002Fbazelbuild\u002Fbazel-gazelle?tab=readme-ov-file#keep-comments","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_45":{"__typename":"Paragraph","id":"540c54e0e4f6_45","name":"f6e2","type":"P","href":null,"layout":null,"metadata":null,"text":"3. There is no effect on runtime.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_46":{"__typename":"Paragraph","id":"540c54e0e4f6_46","name":"028e","type":"P","href":null,"layout":null,"metadata":null,"text":"A small number of tests were unsuitable for Bazel because they required a large view of the repository or a dynamic and implicit set of dependencies. We moved these tests out of our unit testing job to separate CI checks.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_47":{"__typename":"Paragraph","id":"540c54e0e4f6_47","name":"3737","type":"H4","href":null,"layout":null,"metadata":null,"text":"Preventing Backsliding","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_48":{"__typename":"Paragraph","id":"540c54e0e4f6_48","name":"87da","type":"P","href":null,"layout":null,"metadata":null,"text":"With over 20,000 test files and hundreds of people actively working in the same repository, we needed to pursue test fixes such that they would not be undone as product development progressed.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_49":{"__typename":"Paragraph","id":"540c54e0e4f6_49","name":"9301","type":"P","href":null,"layout":null,"metadata":null,"text":"Our CI has three types of build queues:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_50":{"__typename":"Paragraph","id":"540c54e0e4f6_50","name":"128e","type":"P","href":null,"layout":null,"metadata":null,"text":"1. “Required”, which blocks changes,","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_51":{"__typename":"Paragraph","id":"540c54e0e4f6_51","name":"9ed5","type":"P","href":null,"layout":null,"metadata":null,"text":"2. “Optional”, which is non-blocking,","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_52":{"__typename":"Paragraph","id":"540c54e0e4f6_52","name":"24b8","type":"P","href":null,"layout":null,"metadata":null,"text":"3. “Hidden”, which is non-blocking and not shown on PRs.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_53":{"__typename":"Paragraph","id":"540c54e0e4f6_53","name":"d527","type":"P","href":null,"layout":null,"metadata":null,"text":"As we fixed tests, we moved them from “hidden” to “required” via a rule attribute. To ensure a single source of truth, tests run in “required” under Bazel were not run under the Jest setup being replaced.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_54":{"__typename":"Paragraph","id":"540c54e0e4f6_54","name":"c531","type":"PRE","href":null,"layout":null,"metadata":null,"text":"# frontend\u002Fapp\u002Fscript\u002F__tests__\u002FBUILD.bazel\njest_test(\n name = \"jest_test\",\n is_required = True, # makes this target a required check on pull requests \n deps = [\n \":source_library\",\n ],\n)","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"python"},"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_55":{"__typename":"Paragraph","id":"540c54e0e4f6_55","name":"9878","type":"P","href":null,"layout":null,"metadata":null,"text":"Example jest_test rule. This signifies that this target will run on the “required” build queue.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":95,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_56":{"__typename":"Paragraph","id":"540c54e0e4f6_56","name":"347e","type":"P","href":null,"layout":null,"metadata":null,"text":"We wrote a script comparing before and after Bazel to determine migration-readiness, using the metrics of test runtime, code coverage stats, and failure rate. Fortunately, the bulk of tests could be enabled without additional changes, so we enabled these in batches. We divided and conquered the remaining burndown list of failures with the central team, Web Platform, fixing and updating tests in Bazel to avoid putting this burden on our developers. After a grace period, we fully disabled and deleted the non-Bazel Jest infrastructure and removed the is_required param.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_57":{"__typename":"Paragraph","id":"540c54e0e4f6_57","name":"8272","type":"H3","href":null,"layout":null,"metadata":null,"text":"Local Bazel Experience","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_58":{"__typename":"Paragraph","id":"540c54e0e4f6_58","name":"1574","type":"P","href":null,"layout":null,"metadata":null,"text":"In tandem with our CI migration, we ensured that developers can run Bazel locally to reproduce and iterate on CI failures. Our migration principles included delivering only what was on par with or superior to the existing developer experience and performance. JavaScript tools have developer-friendly CLI experiences (e.g., watch mode, targeting select files, rich interactivity) and IDE integrations that we wanted to retain. By default, frontend developers can continue using the tools they know and love, and in cases where it is beneficial they can opt into Bazel. Discrepancies between Bazel and non-Bazel are rare and when they do occur, developers have a means of resolving the issue. For example, developers can run a single script, failed-on-pr which will re-run any targets failing CI locally to easily reproduce issues.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*IqcxStamyg_zPexr":{"__typename":"ImageMetadata","id":"0*IqcxStamyg_zPexr","originalHeight":774,"originalWidth":1600,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:540c54e0e4f6_59":{"__typename":"Paragraph","id":"540c54e0e4f6_59","name":"5ab7","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*IqcxStamyg_zPexr"},"text":"Annotations on a failing build with scripts to recreate the failures, e.g. yak script jest:failed-on-pr","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":103,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_60":{"__typename":"Paragraph","id":"540c54e0e4f6_60","name":"3f8f","type":"P","href":null,"layout":null,"metadata":null,"text":"We also do some normalization of platform specific binaries so that we can reuse the cache between Linux and MacOS builds. This speeds up local development and CI jobs by sharing cache between a local developer’s macbook and linux machines in CI. For native npm packages (node-gyp dependencies) we exclude platform-specific files and build the package on the execution machine. The execution machine will be the machine executing the test or build process. We also use “universal binaries” (e.g., for node and zstd), where all platform binaries are included as inputs (so that inputs are consistent no matter which platform the action is run from) and the proper binary is chosen at runtime.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":272,"end":280,"href":"https:\u002F\u002Fgithub.com\u002Fnodejs\u002Fnode-gyp","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":673,"end":690,"href":"https:\u002F\u002Fgist.github.com\u002Fbrieb\u002F3c0fdb614122e928b4546c5d85c97ab3","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_61":{"__typename":"Paragraph","id":"540c54e0e4f6_61","name":"1f29","type":"H3","href":null,"layout":null,"metadata":null,"text":"Conclusion","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_62":{"__typename":"Paragraph","id":"540c54e0e4f6_62","name":"b136","type":"P","href":null,"layout":null,"metadata":null,"text":"Adopting Bazel for our core CI jobs yielded significant performance improvements for TypeScript type checking (34% faster), ESLint linting (35% faster), and Jest unit tests (42% faster incremental runs, 29% overall). Moreover, our CI can now better scale as the repo grows.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_63":{"__typename":"Paragraph","id":"540c54e0e4f6_63","name":"55c4","type":"P","href":null,"layout":null,"metadata":null,"text":"Next, to further improve Bazel performance, we will be focusing on persisting a warm Bazel host across CI runs, taming our build graph, powering CI jobs that do not use Bazel with the Bazel build graph, and potentially exploring SquashFS to further compress and optimize our Bazel sandboxes.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":229,"end":237,"href":"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSquashFS","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_64":{"__typename":"Paragraph","id":"540c54e0e4f6_64","name":"63a9","type":"P","href":null,"layout":null,"metadata":null,"text":"We hope that sharing our journey has provided insights for organizations considering a Bazel migration for web.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_65":{"__typename":"Paragraph","id":"540c54e0e4f6_65","name":"9171","type":"H3","href":null,"layout":null,"metadata":null,"text":"Acknowledgments","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_66":{"__typename":"Paragraph","id":"540c54e0e4f6_66","name":"453a","type":"P","href":null,"layout":null,"metadata":null,"text":"Thank you Madison Capps, Meghan Dow, Matt Insler, Janusz Kudelka, Joe Lencioni, Rae Liu, James Robinson, Joel Snyder, Elliott Sprehn, Fanying Ye, and various other internal and external partners who helped bring Bazel to Airbnb.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_67":{"__typename":"Paragraph","id":"540c54e0e4f6_67","name":"980b","type":"P","href":null,"layout":null,"metadata":null,"text":"We are also grateful to the broader Bazel community for being welcoming and sharing ideas.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_68":{"__typename":"Paragraph","id":"540c54e0e4f6_68","name":"65fa","type":"H3","href":null,"layout":null,"metadata":null,"text":"****************","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_69":{"__typename":"Paragraph","id":"540c54e0e4f6_69","name":"c4da","type":"P","href":null,"layout":null,"metadata":null,"text":"[1]: This problem is NP-complete, though approximation algorithms have been devised that still guarantee no cycles; we chose the implementation outlined in “Breaking Cycles in Noisy Hierarchies”.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":21,"end":32,"href":"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FNP-completeness","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":129,"end":143,"href":"https:\u002F\u002Fgithub.com\u002Fzhenv5\u002Fbreaking_cycles_in_noisy_hierarchies","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"A","start":157,"end":193,"href":"https:\u002F\u002Fdl.acm.org\u002Fdoi\u002Fpdf\u002F10.1145\u002F3091478.3091495","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_70":{"__typename":"Paragraph","id":"540c54e0e4f6_70","name":"2d1c","type":"P","href":null,"layout":null,"metadata":null,"text":"[2]: After initial evaluation, we considered migrating web asset bundling as out of scope (though we may revisit this in the future) due to high level of effort, unknowns in the bundler landscape, and neutral return on investment given our recent adoption of Metro, as Metro’s architecture already factors in scalability features (e.g. parallelism, local and remote caching, and incremental builds).","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":259,"end":264,"href":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Ffaster-javascript-builds-with-metro-cfc46d617a1f","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_71":{"__typename":"Paragraph","id":"540c54e0e4f6_71","name":"b864","type":"P","href":null,"layout":null,"metadata":null,"text":"[3]: There are newer TS rules that may work well for you here.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":57,"end":61,"href":"https:\u002F\u002Fgithub.com\u002Faspect-build\u002Frules_ts","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_72":{"__typename":"Paragraph","id":"540c54e0e4f6_72","name":"3098","type":"P","href":null,"layout":null,"metadata":null,"text":"[4]: We later switched to using zstd instead of gzip because it produces archives that are better compressed and more deterministic, keeping tarballs consistent across different platforms.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":32,"end":36,"href":"https:\u002F\u002Fgithub.com\u002Ffacebook\u002Fzstd","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_73":{"__typename":"Paragraph","id":"540c54e0e4f6_73","name":"79f9","type":"P","href":null,"layout":null,"metadata":null,"text":"[5]: While unnecessary files may still be included, it’s a much narrower set (and could be pruned as a further optimization).","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:540c54e0e4f6_74":{"__typename":"Paragraph","id":"540c54e0e4f6_74","name":"a7cd","type":"P","href":null,"layout":null,"metadata":null,"text":"All product names, logos, and brands are property of their respective owners. All company, product and service names used in this website are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"EM","start":0,"end":241,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"CollectionViewerEdge:collectionId:53c7c27702d5-viewerId:lo_6d67835949fb":{"__typename":"CollectionViewerEdge","id":"collectionId:53c7c27702d5-viewerId:lo_6d67835949fb","isEditor":false,"isMuting":false},"ImageMetadata:1*JZl-TXoSiG0VmYn3qWLdTA.png":{"__typename":"ImageMetadata","id":"1*JZl-TXoSiG0VmYn3qWLdTA.png","originalWidth":280,"originalHeight":280},"PostViewerEdge:postId:a784b2dbe325-viewerId:lo_6d67835949fb":{"__typename":"PostViewerEdge","shouldIndexPostForExternalSearch":true,"id":"postId:a784b2dbe325-viewerId:lo_6d67835949fb"},"Tag:bazel":{"__typename":"Tag","id":"bazel","displayTitle":"Bazel","normalizedTagSlug":"bazel"},"Tag:web":{"__typename":"Tag","id":"web","displayTitle":"Web","normalizedTagSlug":"web"},"Tag:typescript":{"__typename":"Tag","id":"typescript","displayTitle":"Typescript","normalizedTagSlug":"typescript"},"Tag:migration":{"__typename":"Tag","id":"migration","displayTitle":"Migration","normalizedTagSlug":"migration"},"Tag:engineering":{"__typename":"Tag","id":"engineering","displayTitle":"Engineering","normalizedTagSlug":"engineering"},"Post:a784b2dbe325":{"__typename":"Post","id":"a784b2dbe325","collection":{"__ref":"Collection:53c7c27702d5"},"content({\"postMeteringOptions\":{}})":{"__typename":"PostContent","isLockedPreviewOnly":false,"bodyModel":{"__typename":"RichText","sections":[{"__typename":"Section","name":"d18a","startIndex":0,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null}],"paragraphs":[{"__ref":"Paragraph:540c54e0e4f6_0"},{"__ref":"Paragraph:540c54e0e4f6_1"},{"__ref":"Paragraph:540c54e0e4f6_2"},{"__ref":"Paragraph:540c54e0e4f6_3"},{"__ref":"Paragraph:540c54e0e4f6_4"},{"__ref":"Paragraph:540c54e0e4f6_5"},{"__ref":"Paragraph:540c54e0e4f6_6"},{"__ref":"Paragraph:540c54e0e4f6_7"},{"__ref":"Paragraph:540c54e0e4f6_8"},{"__ref":"Paragraph:540c54e0e4f6_9"},{"__ref":"Paragraph:540c54e0e4f6_10"},{"__ref":"Paragraph:540c54e0e4f6_11"},{"__ref":"Paragraph:540c54e0e4f6_12"},{"__ref":"Paragraph:540c54e0e4f6_13"},{"__ref":"Paragraph:540c54e0e4f6_14"},{"__ref":"Paragraph:540c54e0e4f6_15"},{"__ref":"Paragraph:540c54e0e4f6_16"},{"__ref":"Paragraph:540c54e0e4f6_17"},{"__ref":"Paragraph:540c54e0e4f6_18"},{"__ref":"Paragraph:540c54e0e4f6_19"},{"__ref":"Paragraph:540c54e0e4f6_20"},{"__ref":"Paragraph:540c54e0e4f6_21"},{"__ref":"Paragraph:540c54e0e4f6_22"},{"__ref":"Paragraph:540c54e0e4f6_23"},{"__ref":"Paragraph:540c54e0e4f6_24"},{"__ref":"Paragraph:540c54e0e4f6_25"},{"__ref":"Paragraph:540c54e0e4f6_26"},{"__ref":"Paragraph:540c54e0e4f6_27"},{"__ref":"Paragraph:540c54e0e4f6_28"},{"__ref":"Paragraph:540c54e0e4f6_29"},{"__ref":"Paragraph:540c54e0e4f6_30"},{"__ref":"Paragraph:540c54e0e4f6_31"},{"__ref":"Paragraph:540c54e0e4f6_32"},{"__ref":"Paragraph:540c54e0e4f6_33"},{"__ref":"Paragraph:540c54e0e4f6_34"},{"__ref":"Paragraph:540c54e0e4f6_35"},{"__ref":"Paragraph:540c54e0e4f6_36"},{"__ref":"Paragraph:540c54e0e4f6_37"},{"__ref":"Paragraph:540c54e0e4f6_38"},{"__ref":"Paragraph:540c54e0e4f6_39"},{"__ref":"Paragraph:540c54e0e4f6_40"},{"__ref":"Paragraph:540c54e0e4f6_41"},{"__ref":"Paragraph:540c54e0e4f6_42"},{"__ref":"Paragraph:540c54e0e4f6_43"},{"__ref":"Paragraph:540c54e0e4f6_44"},{"__ref":"Paragraph:540c54e0e4f6_45"},{"__ref":"Paragraph:540c54e0e4f6_46"},{"__ref":"Paragraph:540c54e0e4f6_47"},{"__ref":"Paragraph:540c54e0e4f6_48"},{"__ref":"Paragraph:540c54e0e4f6_49"},{"__ref":"Paragraph:540c54e0e4f6_50"},{"__ref":"Paragraph:540c54e0e4f6_51"},{"__ref":"Paragraph:540c54e0e4f6_52"},{"__ref":"Paragraph:540c54e0e4f6_53"},{"__ref":"Paragraph:540c54e0e4f6_54"},{"__ref":"Paragraph:540c54e0e4f6_55"},{"__ref":"Paragraph:540c54e0e4f6_56"},{"__ref":"Paragraph:540c54e0e4f6_57"},{"__ref":"Paragraph:540c54e0e4f6_58"},{"__ref":"Paragraph:540c54e0e4f6_59"},{"__ref":"Paragraph:540c54e0e4f6_60"},{"__ref":"Paragraph:540c54e0e4f6_61"},{"__ref":"Paragraph:540c54e0e4f6_62"},{"__ref":"Paragraph:540c54e0e4f6_63"},{"__ref":"Paragraph:540c54e0e4f6_64"},{"__ref":"Paragraph:540c54e0e4f6_65"},{"__ref":"Paragraph:540c54e0e4f6_66"},{"__ref":"Paragraph:540c54e0e4f6_67"},{"__ref":"Paragraph:540c54e0e4f6_68"},{"__ref":"Paragraph:540c54e0e4f6_69"},{"__ref":"Paragraph:540c54e0e4f6_70"},{"__ref":"Paragraph:540c54e0e4f6_71"},{"__ref":"Paragraph:540c54e0e4f6_72"},{"__ref":"Paragraph:540c54e0e4f6_73"},{"__ref":"Paragraph:540c54e0e4f6_74"}]},"validatedShareKey":"","shareKeyCreator":null},"creator":{"__ref":"User:1186193d899d"},"inResponseToEntityType":null,"isLocked":false,"isMarkedPaywallOnly":false,"lockedSource":"LOCKED_POST_SOURCE_NONE","mediumUrl":"https:\u002F\u002Fmedium.com\u002Fairbnb-engineering\u002Fadopting-bazel-for-web-at-scale-a784b2dbe325","primaryTopic":null,"topics":[{"__typename":"Topic","slug":"programming"}],"isPublished":true,"latestPublishedVersion":"540c54e0e4f6","visibility":"PUBLIC","postResponses":{"__typename":"PostResponses","count":4},"clapCount":389,"allowResponses":true,"isLimitedState":false,"title":"Adopting Bazel for Web at Scale","isSeries":false,"sequence":null,"uniqueSlug":"adopting-bazel-for-web-at-scale-a784b2dbe325","socialTitle":"","socialDek":"","canonicalUrl":"","metaDescription":"","latestPublishedAt":1731448187261,"readingTime":9.825471698113207,"previewContent":{"__typename":"PreviewContent","subtitle":"How and Why We Migrated Airbnb’s Large-Scale Web Monorepo to Bazel"},"previewImage":{"__ref":"ImageMetadata:1*uMA-yyBcSyRjQBwdQnbDdw.jpeg"},"isShortform":false,"seoTitle":"","firstPublishedAt":1731435737235,"updatedAt":1731655041051,"shortformType":"SHORTFORM_TYPE_LINK","seoDescription":"","viewerEdge":{"__ref":"PostViewerEdge:postId:a784b2dbe325-viewerId:lo_6d67835949fb"},"isSuspended":false,"license":"ALL_RIGHTS_RESERVED","tags":[{"__ref":"Tag:bazel"},{"__ref":"Tag:web"},{"__ref":"Tag:typescript"},{"__ref":"Tag:migration"},{"__ref":"Tag:engineering"}],"isNewsletter":false,"statusForCollection":"APPROVED","pendingCollection":null,"detectedLanguage":"en","wordCount":2352,"layerCake":1,"responsesLocked":false},"ImageMetadata:":{"__typename":"ImageMetadata","id":""}}</script><script>window.__MIDDLEWARE_STATE__={"session":{"xsrf":""},"cache":{"cacheStatus":"HIT"}}</script><script src="https://cdn-client.medium.com/lite/static/js/manifest.b2314f6d.js"></script><script src="https://cdn-client.medium.com/lite/static/js/9865.1496d74a.js"></script><script src="https://cdn-client.medium.com/lite/static/js/main.24534aeb.js"></script><script src="https://cdn-client.medium.com/lite/static/js/instrumentation.d9108df7.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/reporting.ff22a7a5.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9120.5df29668.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5049.d1ead72d.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/4810.6318add7.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6618.db187378.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2707.b0942613.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9977.5b3eb23a.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8599.1ab63137.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5250.9f9e01d2.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6349.b071a958.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2648.26563adf.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8393.826a25fb.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7079.67349d50.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/3735.afb7e926.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5642.a2d9f6a1.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6546.cd03f950.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6834.08de95de.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7346.72622eb9.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2420.2a5e2d95.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/839.ca7937c2.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7975.d195c6f1.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2106.21ff89d3.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7394.3d049572.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2961.00a48598.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8204.c4082863.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/4391.59acaed3.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/PostPage.MainContent.c8a11795.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8414.6565ad5f.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/3974.8d3e0217.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2527.a0afad8a.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/PostResponsesContent.36c2ecf4.chunk.js"></script><script>window.main();</script><script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'8e76d1017f569fb3',t:'MTczMjQyNDIyOC4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>