Created
August 6, 2023 01:10
-
-
Save cindywu/0f1c8197e92fc472ef9e401bbff75456 to your computer and use it in GitHub Desktop.
`curl https://vercel.com/blog/how-turborepo-is-porting-from-go-to-rust | pbcopy`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="next-head" content="1" /> | |
<script> | |
document.documentElement.classList.add("__variable_20951f"); | |
</script> | |
<meta name="next-head" content="1" /> | |
<script> | |
(function () { | |
try { | |
if (document.cookie && document.cookie.indexOf("isLoggedIn=1") > -1) { | |
document.documentElement.classList.add("logged-in-on-page-load"); | |
} | |
} catch (err) {} | |
})(); | |
</script> | |
<meta name="next-head" content="1" /> | |
<title>How Turborepo is porting from Go to Rust – Vercel</title> | |
<meta name="next-head" content="1" /> | |
<meta content="summary_large_image" name="twitter:card" /> | |
<meta name="next-head" content="1" /> | |
<meta content="article" property="og:type" /> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6E56Ftg7W5wsRF8rjgjS7N/42f14e2661f473af626111a517df547e/Turbo_OG.png" | |
name="twitter:image" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="How Turborepo is porting from Go to Rust – Vercel" | |
property="og:title" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="https://vercel.com/blog/how-turborepo-is-porting-from-go-to-rust" | |
property="og:url" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="Our strategy for making updates and maintaining stability while we migrate languages." | |
name="description" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="Our strategy for making updates and maintaining stability while we migrate languages." | |
property="og:description" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6E56Ftg7W5wsRF8rjgjS7N/42f14e2661f473af626111a517df547e/Turbo_OG.png" | |
property="og:image" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta | |
content="max-snippet:-1, max-image-preview:large, max-video-preview:-1" | |
name="robots" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta content="width=device-width, initial-scale=1.0" name="viewport" /> | |
<meta name="next-head" content="1" /> | |
<meta content="en" http-equiv="Content-Language" /> | |
<meta name="next-head" content="1" /> | |
<link | |
crossorigin="anonymous" | |
href="https://assets.vercel.com" | |
rel="preconnect" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
crossorigin="anonymous" | |
href="https://avatars.githubusercontent.com" | |
rel="preconnect" | |
/> | |
<meta name="next-head" content="1" /> | |
<meta content="@vercel" name="twitter:site" /> | |
<meta name="next-head" content="1" /> | |
<meta content="Vercel" name="apple-mobile-web-app-title" /> | |
<meta name="next-head" content="1" /> | |
<meta content="white" name="theme-color" /> | |
<meta name="next-head" content="1" /> | |
<meta content="#000000" name="msapplication-TileColor" /> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/57x57.png" | |
rel="apple-touch-icon" | |
sizes="57x57" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/60x60.png" | |
rel="apple-touch-icon" | |
sizes="60x60" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/72x72.png" | |
rel="apple-touch-icon" | |
sizes="72x72" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/76x76.png" | |
rel="apple-touch-icon" | |
sizes="76x76" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/114x114.png" | |
rel="apple-touch-icon" | |
sizes="114x114" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/120x120.png" | |
rel="apple-touch-icon" | |
sizes="120x120" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/144x144.png" | |
rel="apple-touch-icon" | |
sizes="144x144" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/152x152.png" | |
rel="apple-touch-icon" | |
sizes="152x152" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/180x180.png" | |
rel="apple-touch-icon" | |
sizes="180x180" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/favicon.ico" | |
rel="icon shortcut" | |
type="image/x-icon" | |
/> | |
<meta name="next-head" content="1" /> | |
<link fetchpriority="low" href="/site.webmanifest" rel="manifest" /> | |
<meta name="next-head" content="1" /> | |
<link | |
color="#000000" | |
fetchpriority="low" | |
href="https://assets.vercel.com/image/upload/front/favicon/vercel/safari-pinned-tab.svg" | |
rel="mask-icon" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
href="/atom" | |
rel="alternate" | |
title="Vercel News" | |
type="application/atom+xml" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
rel="preload" | |
as="image" | |
imagesrcset="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F41iFVCHuBs1rQn4jTfRGIt%2F3bd01ff806eb9ab64b31445dad31cd6c%2FOG_Image_____Template__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x" | |
fetchpriority="high" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
rel="preload" | |
as="image" | |
imagesrcset="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F7Ig7FQeMzQ5f0E5f78Qjjp%2F55ab8aec8f2dd11481288e624108785f%2FOG_Image_____Template.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x" | |
fetchpriority="high" | |
/> | |
<meta name="next-head" content="1" /> | |
<link | |
rel="preload" | |
href="/_next/static/media/2aaf0723e720e8b9-s.p.woff2" | |
as="font" | |
type="font/woff2" | |
crossorigin="anonymous" | |
data-next-font="size-adjust" | |
/> | |
<link | |
rel="preload" | |
href="/_next/static/css/877cf5ce5c8aa7a2.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
as="style" | |
/> | |
<link | |
rel="stylesheet" | |
href="/_next/static/css/877cf5ce5c8aa7a2.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
data-n-g="" | |
/> | |
<link | |
rel="preload" | |
href="/_next/static/css/1641b1c074d5669c.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
as="style" | |
/> | |
<link | |
rel="stylesheet" | |
href="/_next/static/css/1641b1c074d5669c.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
data-n-p="" | |
/> | |
<link | |
rel="preload" | |
href="/_next/static/css/3553576d58e33556.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
as="style" | |
/> | |
<link | |
rel="stylesheet" | |
href="/_next/static/css/3553576d58e33556.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
data-n-p="" | |
/> | |
<link | |
rel="preload" | |
href="/_next/static/css/345bbe8b8b390df0.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
as="style" | |
/> | |
<link | |
rel="stylesheet" | |
href="/_next/static/css/345bbe8b8b390df0.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
data-n-p="" | |
/> | |
<link | |
rel="preload" | |
href="/_next/static/css/ecbafbce685c03bc.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
as="style" | |
/> | |
<link | |
rel="stylesheet" | |
href="/_next/static/css/ecbafbce685c03bc.css?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
data-n-p="" | |
/> | |
<noscript data-n-css=""></noscript> | |
<script | |
defer="" | |
nomodule="" | |
src="/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
></script> | |
<script | |
src="/_next/static/chunks/webpack-efd13458723b0b32.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/framework-708b109a51bd6eb9.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/main-31961e64cccd5f70.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/pages/_app-eae4624026105f83.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/82366-18a5bd1b0f4b7a9a.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/57861-94134e8b604f5cba.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/16981-54155d51085edce5.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/48075-1a187ca4710a1814.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/81050-17f432c087fca463.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/38620-b5c278cc3d9a59c7.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/35795-158f8110617e1a00.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/59379-694786c2e19cdab3.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/8672-3a3fff3859bd7a76.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/63627-6fabe6f58991f131.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/21443-f7d451d45c9df1fb.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/74162-3a9ad2e30ae22d73.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/32605-8bda135b13ffcbf0.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/81604-570fa3af6577d7b1.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/35338-407670dd7ce45aff.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/27632-8748bd344333fc69.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/37134-0c4b94259675cf00.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/20113-853930ac1fd641c6.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/2202-dea9f92c89c4caa3.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/806-41f43425ce04c8a7.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/45408-9d08073890bbfa13.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/chunks/pages/blog/%5Bslug%5D-76b054059d4115af.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/iFgjysQlqiq_betPy2rns/_buildManifest.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
<script | |
src="/_next/static/iFgjysQlqiq_betPy2rns/_ssgManifest.js?dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
defer="" | |
></script> | |
</head> | |
<body> | |
<div id="__next"> | |
<a | |
class="skip-nav-link_skipLink__M2lut" | |
href="#geist-skip-nav" | |
tabindex="0" | |
>Skip to content</a | |
> | |
<script> | |
!(function () { | |
try { | |
var d = document.documentElement, | |
c = d.classList; | |
c.remove("light-theme", "dark-theme"); | |
var e = localStorage.getItem("zeit-theme"); | |
if ("system" === e || (!e && true)) { | |
var t = "(prefers-color-scheme: dark)", | |
m = window.matchMedia(t); | |
if (m.media !== t || m.matches) { | |
d.style.colorScheme = "dark"; | |
c.add("dark-theme"); | |
} else { | |
d.style.colorScheme = "light"; | |
c.add("light-theme"); | |
} | |
} else if (e) { | |
var x = { light: "light-theme", dark: "dark-theme" }; | |
c.add(x[e] || ""); | |
} | |
if (e === "light" || e === "dark") d.style.colorScheme = e; | |
} catch (e) {} | |
})(); | |
</script> | |
<div class="geist-page"> | |
<span class="dark-theme invert-theme"></span> | |
<div class="screen_geist_screen__Hlyrv"> | |
<div class="header_wrapper__u5Opu"> | |
<header | |
class="header_header__dGL52" | |
style="--full: var(--ds-page-width-with-margin)" | |
> | |
<div class="header_nav_first__PxyqC"> | |
<div> | |
<a | |
aria-controls="menu-:Rlcbsla6:" | |
aria-expanded="false" | |
aria-label="dashboard" | |
class="logo-context-menu-button_button__EKpor" | |
data-testid="header/navbar/logo" | |
id="menu-button-:Rlcbsla6H1:" | |
href="/dashboard" | |
><svg | |
aria-label="Vercel logotype" | |
height="22" | |
role="img" | |
viewBox="0 0 283 64" | |
> | |
<path | |
d="M141.68 16.25c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm117.14-14.5c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm-39.03 3.5c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9v-46h9zM37.59.25l36.95 64H.64l36.95-64zm92.38 5l-27.71 48-27.71-48h10.39l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10v14.8h-9v-34h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" | |
fill="var(--geist-foreground)" | |
></path></svg | |
></a> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: row; | |
--stack-align: center; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 8px; | |
" | |
> | |
<button | |
aria-label="open menu" | |
class="menu-toggle_menuToggle__6OaWw mobile-menu_indicator__le_Ik" | |
data-testid="mobile-menu/trigger" | |
type="button" | |
> | |
<div class="menu-toggle_wrap__I3ShB"></div> | |
</button> | |
</div> | |
</div> | |
<div class="header_nav_second__cIFf5"> | |
<nav | |
aria-label="Navigation header with 5 links and 1 dropdown menu with links" | |
data-orientation="horizontal" | |
dir="ltr" | |
class="navigation-menu_root__raIWR" | |
> | |
<div style="position: relative"> | |
<ul | |
data-orientation="horizontal" | |
class="navigation-menu_list__sVWMG" | |
dir="ltr" | |
> | |
<li> | |
<button | |
id="radix-:R9cbsla6:-trigger-features" | |
data-state="closed" | |
aria-expanded="false" | |
aria-controls="radix-:R9cbsla6:-content-features" | |
class="navigation-menu_trigger__bhwIG" | |
data-active="false" | |
data-radix-collection-item="" | |
> | |
Features<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
aria-hidden="true" | |
style="color: currentColor" | |
> | |
<path d="M6 9l6 6 6-6" /> | |
</svg> | |
</button> | |
<div | |
id="radix-:R9cbsla6:-content-features" | |
aria-labelledby="radix-:R9cbsla6:-trigger-features" | |
data-orientation="horizontal" | |
data-state="closed" | |
class="navigation-menu_content__PoN9R" | |
style="pointer-events: none" | |
dir="ltr" | |
> | |
<ul | |
class="navigation-menu_features__oDdcA navigation-menu_withStorage__MV_0y" | |
> | |
<li | |
class="navigation-menu_featuresLinks__KBJZ4 navigation-menu_withStorage__MV_0y" | |
> | |
<a | |
class="navigation-menu_menuSubLink__YIrPI" | |
data-feature="Previews" | |
data-radix-collection-item="" | |
href="/features/previews" | |
> | |
<div | |
class="navigation-menu_menuItemHeading__4RlM1" | |
> | |
<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor" | |
> | |
<path | |
d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" | |
/></svg | |
>Previews | |
</div> | |
<p | |
class="text_wrapper__i87JK navigation-menu_menuItemText__op6fR" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Zero config, more innovation | |
</p> </a | |
><a | |
class="navigation-menu_menuSubLink__YIrPI" | |
data-feature="Infrastructure" | |
data-radix-collection-item="" | |
href="/features/infrastructure" | |
> | |
<div | |
class="navigation-menu_menuItemHeading__4RlM1" | |
> | |
<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor" | |
> | |
<path d="M12 2L2 7l10 5 10-5-10-5z" /> | |
<path d="M2 17l10 5 10-5" /> | |
<path d="M2 12l10 5 10-5" /></svg | |
>Infrastructure | |
</div> | |
<p | |
class="text_wrapper__i87JK navigation-menu_menuItemText__op6fR" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Always fast, always online | |
</p> </a | |
><a | |
class="navigation-menu_menuSubLink__YIrPI" | |
data-feature="Edge Functions" | |
data-radix-collection-item="" | |
href="/features/edge-functions" | |
> | |
<div | |
class="navigation-menu_menuItemHeading__4RlM1" | |
> | |
<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor" | |
> | |
<circle cx="12" cy="12" r="10" /> | |
<path d="M2 12h20" /> | |
<path | |
d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" | |
/></svg | |
>Edge Functions | |
</div> | |
<p | |
class="text_wrapper__i87JK navigation-menu_menuItemText__op6fR" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Dynamic pages, static speed | |
</p> </a | |
><a | |
class="navigation-menu_menuSubLink__YIrPI" | |
data-feature="Next.js" | |
data-radix-collection-item="" | |
href="/solutions/nextjs" | |
> | |
<div | |
class="navigation-menu_menuItemHeading__4RlM1" | |
> | |
<svg | |
aria-label="Next.js logomark" | |
class="next-mark_root__iLw9v" | |
height="80" | |
role="img" | |
viewBox="0 0 180 180" | |
width="80" | |
> | |
<mask | |
height="180" | |
id=":Rb55pcbsla6:mask0_408_134" | |
maskUnits="userSpaceOnUse" | |
style="mask-type: alpha" | |
width="180" | |
x="0" | |
y="0" | |
> | |
<circle | |
cx="90" | |
cy="90" | |
fill="black" | |
r="90" | |
></circle> | |
</mask> | |
<g | |
mask="url(#:Rb55pcbsla6:mask0_408_134)" | |
> | |
<circle | |
cx="90" | |
cy="90" | |
data-circle="true" | |
fill="black" | |
r="90" | |
></circle> | |
<path | |
d="M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z" | |
fill="url(#:Rb55pcbsla6:paint0_linear_408_134)" | |
></path> | |
<rect | |
fill="url(#:Rb55pcbsla6:paint1_linear_408_134)" | |
height="72" | |
width="12" | |
x="115" | |
y="54" | |
></rect> | |
</g> | |
<defs> | |
<linearGradient | |
gradientUnits="userSpaceOnUse" | |
id=":Rb55pcbsla6:paint0_linear_408_134" | |
x1="109" | |
x2="144.5" | |
y1="116.5" | |
y2="160.5" | |
> | |
<stop stop-color="white"></stop> | |
<stop | |
offset="1" | |
stop-color="white" | |
stop-opacity="0" | |
></stop> | |
</linearGradient> | |
<linearGradient | |
gradientUnits="userSpaceOnUse" | |
id=":Rb55pcbsla6:paint1_linear_408_134" | |
x1="121" | |
x2="120.799" | |
y1="54" | |
y2="106.875" | |
> | |
<stop stop-color="white"></stop> | |
<stop | |
offset="1" | |
stop-color="white" | |
stop-opacity="0" | |
></stop> | |
</linearGradient> | |
</defs></svg | |
>Next.js | |
</div> | |
<p | |
class="text_wrapper__i87JK navigation-menu_menuItemText__op6fR" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
The native Next.js platform | |
</p> </a | |
><a | |
class="navigation-menu_menuSubLink__YIrPI" | |
data-feature="Analytics" | |
data-radix-collection-item="" | |
href="/analytics" | |
> | |
<div | |
class="navigation-menu_menuItemHeading__4RlM1" | |
> | |
<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor" | |
> | |
<path | |
d="M22 12h-4l-3 9L9 3l-3 9H2" | |
/></svg | |
>Analytics | |
</div> | |
<p | |
class="text_wrapper__i87JK navigation-menu_menuItemText__op6fR" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Real-time insights, peak performance | |
</p> </a | |
><a | |
class="navigation-menu_menuSubLink__YIrPI" | |
data-feature="Storage" | |
data-radix-collection-item="" | |
href="/storage/kv" | |
> | |
<div | |
class="navigation-menu_menuItemHeading__4RlM1" | |
> | |
<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor" | |
> | |
<ellipse cx="12" cy="5" rx="9" ry="3" /> | |
<path | |
d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" | |
/> | |
<path | |
d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" | |
/></svg | |
>Storage | |
</div> | |
<p | |
class="text_wrapper__i87JK navigation-menu_menuItemText__op6fR" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Serverless storage for the frontend | |
</p> | |
</a> | |
</li> | |
</ul> | |
</div> | |
</li> | |
<li> | |
<a | |
class="navigation-menu_link__RaML8" | |
data-active="false" | |
href="/docs" | |
>Docs</a | |
> | |
</li> | |
<li> | |
<a | |
class="navigation-menu_link__RaML8" | |
data-active="false" | |
href="/templates" | |
>Templates</a | |
> | |
</li> | |
<li> | |
<a | |
class="navigation-menu_link__RaML8" | |
data-active="false" | |
href="/integrations" | |
>Integrations</a | |
> | |
</li> | |
<li> | |
<a | |
class="navigation-menu_link__RaML8" | |
data-active="false" | |
href="/customers" | |
>Customers</a | |
> | |
</li> | |
<li> | |
<a | |
class="navigation-menu_link__RaML8" | |
data-active="false" | |
href="/enterprise" | |
>Enterprise</a | |
> | |
</li> | |
<li> | |
<a | |
class="navigation-menu_link__RaML8" | |
data-active="false" | |
href="/pricing" | |
>Pricing</a | |
> | |
</li> | |
</ul> | |
</div> | |
<div class="navigation-menu_viewportPosition__rATSg"></div> | |
</nav> | |
</div> | |
</div> | |
<div class="header_nav_third__ZqUtx"> | |
<div class="header_right__Zq0mQ header_hidden__AkePn"> | |
<span | |
class="fade-in_fade-in__H0uu5" | |
style="box-sizing: border-box" | |
> | |
<div class="logged-out-profile_wrapper__n5dBO"> | |
<div class="logged-out-profile_links__fi18g"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/contact/sales" | |
>Contact</a | |
><a | |
role="link" | |
tabindex="0" | |
href="/login" | |
type="submit" | |
data-testid="header/login" | |
class="button_base__BjwbK reset_reset__KRyvc button_button__81573 reset_reset__KRyvc button_secondary__kMMNc button_small__iQMBm button_invert__YNhnn" | |
data-geist-button="" | |
data-version="v1" | |
style="--geist-icon-size: 16px" | |
><span class="button_content__1aE1_">Log In</span></a | |
> | |
</div> | |
<a | |
role="link" | |
tabindex="0" | |
href="/signup" | |
type="submit" | |
data-testid="header/sign-up" | |
style="--content-width: 60px; --geist-icon-size: 16px" | |
class="button_base__BjwbK reset_reset__KRyvc button_button__81573 reset_reset__KRyvc button_small__iQMBm button_invert__YNhnn" | |
data-geist-button="" | |
data-version="v1" | |
><span class="button_content__1aE1_"> | |
<div | |
class="logged-out-profile_morphingButtonContent__W2wju" | |
> | |
Sign Up | |
</div> | |
<div | |
class="logged-out-profile_morphingButtonContentWidth__LAe9w" | |
></div> </span | |
></a> | |
</div> | |
</span> | |
</div> | |
</div> | |
</header> | |
</div> | |
<div id="geist-skip-nav" tabindex="-1"></div> | |
<!--$--> | |
<main> | |
<div | |
class="article-layout_headerWrapper__8Ymr6" | |
style="--gradient-from: 0 112 243; --gradient-to: 248 28 229" | |
> | |
<div | |
class="article-layout_header__o8oEb article-layout_headerWithHeroImage__F5PZG" | |
> | |
<div class="article-layout_backButtonWrapper__5ps8B"> | |
<div class="article-layout_backButton__QhgkK"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/blog/category/engineering" | |
>← Back to | |
<!-- -->Blog</a | |
> | |
</div> | |
</div> | |
<div class="article-layout_titleWrapper__qBh83"> | |
<div class="article-layout_categoryWrapper__IAl49"> | |
<a | |
class="link_link__hbWKh" | |
href="/blog/category/engineering" | |
> | |
<div | |
class="article-layout_pill__thS5K article-layout_pillGradientBackground__7hvLP" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Engineering | |
</p> | |
</div> | |
</a> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Friday, July 21st 2023 | |
</p> | |
</div> | |
<h1 class="article-layout_title__LKJz3"> | |
<span | |
data-br=":R1kb3sla6:" | |
data-brr="1" | |
style=" | |
display: inline-block; | |
vertical-align: top; | |
text-decoration: inherit; | |
text-wrap: balance; | |
" | |
>How Turborepo is porting from Go to Rust</span | |
> | |
<script> | |
self.__wrap_n = | |
self.__wrap_n || | |
(self.CSS && CSS.supports("text-wrap", "balance") | |
? 1 | |
: 2); | |
self.__wrap_b = (t, n, e) => { | |
e = e || document.querySelector(`[data-br="${t}"]`); | |
let s = e.parentElement, | |
r = (O) => (e.style.maxWidth = O + "px"); | |
e.style.maxWidth = ""; | |
let o = s.clientWidth, | |
u = s.clientHeight, | |
a = o / 2 - 0.25, | |
l = o + 0.5, | |
d; | |
if (o) { | |
for ( | |
r(a), a = Math.max(e.scrollWidth, a); | |
a + 1 < l; | |
) | |
(d = Math.round((a + l) / 2)), | |
r(d), | |
s.clientHeight === u ? (l = d) : (a = d); | |
r(l * n + o * (1 - n)); | |
} | |
e.__wrap_o || | |
(typeof ResizeObserver != "undefined" | |
? (e.__wrap_o = new ResizeObserver(() => { | |
self.__wrap_b(0, +e.dataset.brr, e); | |
})).observe(s) | |
: process.env.NODE_ENV === "development" && | |
console.warn( | |
"The browser you are using does not support the ResizeObserver API. Please consider add polyfill for this API to avoid potential layout shifts or upgrade your browser. Read more: https://github.com/shuding/react-wrap-balancer#browser-support-information" | |
)); | |
}; | |
self.__wrap_n != 1 && self.__wrap_b(":R1kb3sla6:", 1); | |
</script> | |
</h1> | |
<p class="article-layout_subtitle__yRQHC"> | |
<span | |
data-br=":R1sb3sla6:" | |
data-brr="1" | |
style=" | |
display: inline-block; | |
vertical-align: top; | |
text-decoration: inherit; | |
text-wrap: balance; | |
" | |
>Our strategy for making updates and maintaining stability | |
while we migrate languages.</span | |
> | |
<script> | |
self.__wrap_n = | |
self.__wrap_n || | |
(self.CSS && CSS.supports("text-wrap", "balance") | |
? 1 | |
: 2); | |
self.__wrap_b = (t, n, e) => { | |
e = e || document.querySelector(`[data-br="${t}"]`); | |
let s = e.parentElement, | |
r = (O) => (e.style.maxWidth = O + "px"); | |
e.style.maxWidth = ""; | |
let o = s.clientWidth, | |
u = s.clientHeight, | |
a = o / 2 - 0.25, | |
l = o + 0.5, | |
d; | |
if (o) { | |
for ( | |
r(a), a = Math.max(e.scrollWidth, a); | |
a + 1 < l; | |
) | |
(d = Math.round((a + l) / 2)), | |
r(d), | |
s.clientHeight === u ? (l = d) : (a = d); | |
r(l * n + o * (1 - n)); | |
} | |
e.__wrap_o || | |
(typeof ResizeObserver != "undefined" | |
? (e.__wrap_o = new ResizeObserver(() => { | |
self.__wrap_b(0, +e.dataset.brr, e); | |
})).observe(s) | |
: process.env.NODE_ENV === "development" && | |
console.warn( | |
"The browser you are using does not support the ResizeObserver API. Please consider add polyfill for this API to avoid potential layout shifts or upgrade your browser. Read more: https://github.com/shuding/react-wrap-balancer#browser-support-information" | |
)); | |
}; | |
self.__wrap_n != 1 && self.__wrap_b(":R1sb3sla6:", 1); | |
</script> | |
</p> | |
</div> | |
</div> | |
</div> | |
<div class="utils_mobileOnly__pEh2w"> | |
<div> | |
<div class="article-layout_heroImageWrapper__h98kQ"> | |
<div class="themed-image_themedImage__BlQmv"> | |
<img | |
data-version="v1" | |
alt="" | |
fetchpriority="high" | |
width="2529" | |
height="1323" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le image_lightMode__mjeK6" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2529 1323'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAYFBMVEXm8v/t8f316/b73+v+0uP/yN//wd//wuLM5f2j2vSr3fLh6vb96vT8v637qJT/wuXO4f+Z0/Og2PLi7vv68/vzt6nvn5b/yerS3//X3v/h4v/r6P/z7P356vn+4vT/3PP2FRcUAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFAcRuQE4zgAAABJJREFUCNdjZGCEAg4ozYLBAAAEUQA/unUekQAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F41iFVCHuBs1rQn4jTfRGIt%2F3bd01ff806eb9ab64b31445dad31cd6c%2FOG_Image_____Template__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x | |
" | |
src="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F41iFVCHuBs1rQn4jTfRGIt%2F3bd01ff806eb9ab64b31445dad31cd6c%2FOG_Image_____Template__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/><img | |
data-version="v1" | |
alt="" | |
fetchpriority="high" | |
width="2529" | |
height="1323" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le image_darkMode__UWwNT" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2529 1323'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAYFBMVEUPGSQQFB4XDxgoEBo6FCNIGS1PGjNOGjUYL0QQRl0MPlISHCceDhdoLxuLPSZSHTsgMUoVTmwPR18NGSQTDBReJBV3Kh9JGzcjLkokKkYgITobGSwYEiEdEBwnEB8tECOoD8MqAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFAcSIAhpdAAAABJJREFUCNdjZGCEAg4ozYLBAAAEUQA/unUekQAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F7Ig7FQeMzQ5f0E5f78Qjjp%2F55ab8aec8f2dd11481288e624108785f%2FOG_Image_____Template.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x | |
" | |
src="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F7Ig7FQeMzQ5f0E5f78Qjjp%2F55ab8aec8f2dd11481288e624108785f%2FOG_Image_____Template.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="utils_mobileOnly__pEh2w"> | |
<div style="padding: 24px 24px 0"> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Posted by | |
</p> | |
<div class="article-layout_authorList__46xD0"> | |
<div class="article-layout_authorListContainer__1AOq0"> | |
<div class="article-layout_authorListBorder__Grgtb"> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: row; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 16px; | |
" | |
> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
min-height: 40px; | |
--stack-flex: initial; | |
--stack-direction: row; | |
--stack-align: center; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 12px; | |
" | |
data-vercel-edit-target="true" | |
> | |
<span | |
aria-label="Avatar for nicholaslyang" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 36px" | |
><img | |
data-version="v1" | |
alt="Avatar for nicholaslyang" | |
title="Avatar for nicholaslyang" | |
loading="eager" | |
width="36" | |
height="36" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=nicholaslyang&s=72" | |
/></span> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 2px; | |
" | |
> | |
<a | |
href="https://x.com/NicholasLYang" | |
rel="noopener noreferrer" | |
style="text-decoration: none" | |
target="_blank" | |
> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
letter-spacing: -0.01em; | |
margin-right: 4px; | |
" | |
> | |
Nicholas Yang | |
</p> | |
</a> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
min-height: 16px; | |
letter-spacing: -0.01em; | |
" | |
> | |
Turborepo Core Team | |
</p> | |
</div> | |
</div> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
min-height: 40px; | |
--stack-flex: initial; | |
--stack-direction: row; | |
--stack-align: center; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 12px; | |
" | |
data-vercel-edit-target="true" | |
> | |
<span | |
aria-label="Avatar for anthony-shew" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 36px" | |
><img | |
data-version="v1" | |
alt="Avatar for anthony-shew" | |
title="Avatar for anthony-shew" | |
loading="eager" | |
width="36" | |
height="36" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=anthony-shew&s=72" | |
/></span> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 2px; | |
" | |
> | |
<a | |
href="https://x.com/anthonysheww" | |
rel="noopener noreferrer" | |
style="text-decoration: none" | |
target="_blank" | |
> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
letter-spacing: -0.01em; | |
margin-right: 4px; | |
" | |
> | |
Anthony Shew | |
</p> | |
</a> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
min-height: 16px; | |
letter-spacing: -0.01em; | |
" | |
> | |
Content Engineer | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="article-layout_authorListOverlay__ai0FQ"></div> | |
</div> | |
</div> | |
</div> | |
<div class="article-layout_articleLayout__8C1Vn"> | |
<div class="article-layout_articleWrapper__ztnV7"> | |
<div class="utils_desktopOnly__RlogC"> | |
<div> | |
<div class="article-layout_heroImageWrapper__h98kQ"> | |
<div class="themed-image_themedImage__BlQmv"> | |
<img | |
data-version="v1" | |
alt="" | |
fetchpriority="high" | |
width="2529" | |
height="1323" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le image_lightMode__mjeK6" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2529 1323'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAYFBMVEXm8v/t8f316/b73+v+0uP/yN//wd//wuLM5f2j2vSr3fLh6vb96vT8v637qJT/wuXO4f+Z0/Og2PLi7vv68/vzt6nvn5b/yerS3//X3v/h4v/r6P/z7P356vn+4vT/3PP2FRcUAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFAcRuQE4zgAAABJJREFUCNdjZGCEAg4ozYLBAAAEUQA/unUekQAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F41iFVCHuBs1rQn4jTfRGIt%2F3bd01ff806eb9ab64b31445dad31cd6c%2FOG_Image_____Template__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x | |
" | |
src="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F41iFVCHuBs1rQn4jTfRGIt%2F3bd01ff806eb9ab64b31445dad31cd6c%2FOG_Image_____Template__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/><img | |
data-version="v1" | |
alt="" | |
fetchpriority="high" | |
width="2529" | |
height="1323" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le image_darkMode__UWwNT" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2529 1323'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAYFBMVEUPGSQQFB4XDxgoEBo6FCNIGS1PGjNOGjUYL0QQRl0MPlISHCceDhdoLxuLPSZSHTsgMUoVTmwPR18NGSQTDBReJBV3Kh9JGzcjLkokKkYgITobGSwYEiEdEBwnEB8tECOoD8MqAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFAcSIAhpdAAAABJJREFUCNdjZGCEAg4ozYLBAAAEUQA/unUekQAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F7Ig7FQeMzQ5f0E5f78Qjjp%2F55ab8aec8f2dd11481288e624108785f%2FOG_Image_____Template.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x | |
" | |
src="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F7Ig7FQeMzQ5f0E5f78Qjjp%2F55ab8aec8f2dd11481288e624108785f%2FOG_Image_____Template.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="post slug_post__mPVtB"> | |
<p class="renderers_paragraph__ebcQ5"> | |
In | |
<a | |
href="https://vercel.com/blog/turborepo-migration-go-rust" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>a previous blog post</a | |
>, we talked about <b>why</b> we are porting | |
<a | |
href="https://turbo.build?utm_source=turbo&utm_medium=blog&utm_campaign=turborepo_porting" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>Turborepo, the high-performance build system for | |
JavaScript and TypeScript</a | |
>, from Go to Rust. Now, let's talk about <b>how</b>. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Today, our porting effort is in full swing, moving more and | |
more code to Rust. But when we were starting out, we had to | |
make sure that porting was feasible for us to accomplish. A | |
migration from one language to another is no small task and | |
there's a lot of research to do up front to ensure that | |
the end goal is attainable. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Here’s how we started the process, validated our current | |
porting strategy, and made the call to port Turborepo to | |
Rust. | |
</p> | |
<div id="preview-divider"></div> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span | |
class="heading_target__nEYko" | |
id="port-vs.-full-rewrite" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#port-vs.-full-rewrite" | |
>Port vs. full rewrite</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
When we were planning our migration, we briefly considered a | |
full, ground-up rewrite. But, talking the idea through, we | |
realized it wouldn't fit our goals as well as an | |
incremental port would. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="what-is-an-incremental-port" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#what-is-an-incremental-port" | |
>What is an incremental port?</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
Incremental porting moves code piece-by-piece, running new | |
and old code together at the same time. The goal for the | |
chunk of code being moved is to keep the behavior exactly | |
the same as before it was ported. | |
</p> | |
<div class="geist-show-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319409.png" | |
loading="lazy" | |
width="2880" | |
height="2113" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2880 2113'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAS1BMVEX////0+v38/Pz9/f79/v7+/f7Z8fjk9fr++vn99fb98uz9/v/++/z9/P399/X99vT//v/+/v/y+Pzx+P3+/v7a7fLo7Or99fP99vPNGza/AAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFA8e4WevVwAAAC1JREFUCB0FwTEKgDAAALFL1U0E///IDq6lmEhiuRB0+3pQL3t21sGuUWuMWT+RPwcYUMRZIAAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FmZz2Um8re3MZXucpSZdoV%2F890469916054b2f0b364c13514e825b4%2FFrame_427319409.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FmZz2Um8re3MZXucpSZdoV%2F890469916054b2f0b364c13514e825b4%2FFrame_427319409.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319402 (1).png" | |
loading="lazy" | |
width="1920" | |
height="1409" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 1409'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAaVBMVEUDAgMLEBYIERcEBQcGBQYMCg8JDBEBAwUGAwUPJy8PICcKBggLBQYbEhMjFxEEBAgEAQMSCg4RCg4DAgQaDQ0aDg4EAgQAAAABAAEEAwUMERgJEhkCAwYHAwcXKC0gIyIFAgMbDQ0ZDg6TTIBOAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFA8e4WevVwAAAClJREFUCB0FwcENwCAQA7C4jVT2X5Vv0WELwAJ04ZR+4J8n03ZPmrw0uXVZBkK3hzI4AAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2Fc17bo47fBBuBQztRawrOp%2Fe16450ef2ef20d0c7bf2dc709912fc1b%2FFrame_427319402__1_.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2Fc17bo47fBBuBQztRawrOp%2Fe16450ef2ef20d0c7bf2dc709912fc1b%2FFrame_427319402__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2Fc17bo47fBBuBQztRawrOp%2Fe16450ef2ef20d0c7bf2dc709912fc1b%2FFrame_427319402__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<div class="geist-hide-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 6.png" | |
loading="lazy" | |
width="621" | |
height="628" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 621 628'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAilBMVEX//v/+/v/39/j29vf+///59/zl8vro9Pv4+/77+fz5+Pz3+f33+v758/nK7vfk9fr28fT78fP99/j+8Or29/z88/j99fj79fn7+Pv9+Pv87Or76uX69vr//f7++vz49/z2+P39+/z+/v7////69vvZ8fnb8ff1+P3l6eb159z49vv89Pj79Pj8+vx82KiOAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFA8e4WevVwAAADVJREFUCB0FwcENgDAMBLBzU9rswf6zIcEXIWwRvpQDMBswGzC6772vtZyBNyN5qiqZyS6SH7VHBXj74U6fAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F6ZaHYc4GhEIKfRL2c1erpl%2F59f7fc484abd77f372331aec1f583d84%2FiPhone_8_Plus_-_6.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F6ZaHYc4GhEIKfRL2c1erpl%2F59f7fc484abd77f372331aec1f583d84%2FiPhone_8_Plus_-_6.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F6ZaHYc4GhEIKfRL2c1erpl%2F59f7fc484abd77f372331aec1f583d84%2FiPhone_8_Plus_-_6.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 2.png" | |
loading="lazy" | |
width="621" | |
height="628" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 621 628'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAkFBMVEUBAQELCwsMDAwKCA0RHigPHSYCBAcGBAcTERcQExoHCg8QCw8TJCsPCQsSCAocFRYMDRIOBwsYEBMYERUGAwYHAwUmFhMrGBMMCQwBAQEDAgMEAwUHBgsFBwwEAwYBAQEMCA0RKTITKjIHCxABAQEOBwwLCQ4BAQEGAwQRCAwQCQwFAwUOMzwqGhIlKCMzIxbky/qKAAAALHRSTlPP0NHb6+rW1+rr2+X+397+5ef19ePj9fXn6enp6urp7/H9/fH19vf8/P39/IzNUJEAAAAJcEhZcwAAITgAACE4AUWWMWAAAAAHdElNRQfnBw0UDx7hZ69XAAAAOElEQVQIHQXBQQ5AQBQFsFfzR7BzBAv3vxyxMAltkpiSaIC2nVdVHbcdUAsYX80BqR6rR6rFq4/8xz8HnPZXm+EAAAAASUVORK5CYII='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1b7106hvxHFzTGUpGnkJtO%2F33e299c2b1216be88278d462c085e32e%2FiPhone_8_Plus_-_2.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1b7106hvxHFzTGUpGnkJtO%2F33e299c2b1216be88278d462c085e32e%2FiPhone_8_Plus_-_2.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1b7106hvxHFzTGUpGnkJtO%2F33e299c2b1216be88278d462c085e32e%2FiPhone_8_Plus_-_2.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<p class="renderers_paragraph__ebcQ5"> | |
In our case, this means we need to have our Go code and Rust | |
code interoperating with each other. We want to do a simple | |
translation, explicitly avoiding making improvements or | |
changing functionality when we're swapping out | |
languages for the slice of code. That way, we can do | |
intensive testing against both sets of code, and complete | |
the migration as quickly as possible. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="why-we-didn't-do-a-full-rewrite" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#why-we-didn't-do-a-full-rewrite" | |
>Why we didn't do a full rewrite</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
Full rewrites are very tempting. They are more simple to | |
write and ship, as you don't need to worry about your | |
"before" and "after" code working | |
together. You also get a clean slate to write a new and | |
improved version, without the warts and technical debt of | |
the previous iteration. However, full rewrites also come | |
with some serious downsides. | |
</p> | |
<div class="geist-show-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319410.png" | |
loading="lazy" | |
width="1920" | |
height="735" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 735'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAMAAACZFr56AAAAM1BMVEX////29vf3+Pr6+vr5+fn+/v7S7/fW8fn9/f/+/f7+8Of+7uP//v/o9Pnq9fr+9vH+9O70/J92AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBQCXFA4ggAAACBJREFUCB1jZGBkZGRgZPjDwsoIBn9Y2BkZPzHwMzIAACNfAxyhf+CXAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7nb5vSuSQZXEKsUUsYGNVl%2Ff0f6be9ab08484fb087d5583d5720278%2FFrame_427319410.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7nb5vSuSQZXEKsUUsYGNVl%2Ff0f6be9ab08484fb087d5583d5720278%2FFrame_427319410.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7nb5vSuSQZXEKsUUsYGNVl%2Ff0f6be9ab08484fb087d5583d5720278%2FFrame_427319410.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319404 (1).png" | |
loading="lazy" | |
width="1920" | |
height="735" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 735'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAMAAACZFr56AAAARVBMVEUCAQMPDhMKDRIGBwgIBwgODRIMDhQBAgMHAwgPLDUNKTIEBQkIAwgpGREqGxEGAgUSGiERGCAEAgQFAgQhEQ8iEg8FAwVv/dKEAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBQCXFA4ggAAABRJREFUCNdjZGCEAg4I9ZeFHSoAAAwVASfKYVlVAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7bF6sXF2wD092FiiuBXyGV%2Ff5f57209b1f25030cb3e106a2a6189d1%2FFrame_427319404__1_.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7bF6sXF2wD092FiiuBXyGV%2Ff5f57209b1f25030cb3e106a2a6189d1%2FFrame_427319404__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7bF6sXF2wD092FiiuBXyGV%2Ff5f57209b1f25030cb3e106a2a6189d1%2FFrame_427319404__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<div class="geist-hide-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 11.png" | |
loading="lazy" | |
width="414" | |
height="459" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 459'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAJCAMAAAA4jZ0cAAAAWlBMVEX////x8fHw8PD9/P7u7fLs7/T8/f/59PrS8fjT8fn09/z78/jU8fn39fr+/P388/f9/P39+/7v7vPu8Pb7/P/+7eL+7+X09vz97eX97ub49fr99vn89vr+/f5o6E+ZAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBQCXFA4ggAAADFJREFUCB0FwbERgEAAAyDyybn/tBYWKkAOQpM8xpI8xt6GQ6+vjGbhcLdlrAmjTfgB9MQFSOp6R8wAAAAASUVORK5CYII='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F365RREyHsEytwZlyGKa985%2F24ca1793a87a50ac37bdfea84c2964a5%2FiPhone_8_Plus_-_11.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F365RREyHsEytwZlyGKa985%2F24ca1793a87a50ac37bdfea84c2964a5%2FiPhone_8_Plus_-_11.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F365RREyHsEytwZlyGKa985%2F24ca1793a87a50ac37bdfea84c2964a5%2FiPhone_8_Plus_-_11.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 16.png" | |
loading="lazy" | |
width="414" | |
height="459" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 459'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAJCAMAAAA4jZ0cAAAAYFBMVEUAAAAQEBADAgQSERcPExkBAwUNCA8PLTUPLTYHCxEPBw0PLDQPKzQLCQ4EAQMPBgoOBgsDAgMDAgUTEhgQExkCAwYMBw0uGw8sGxAHCg8NBgotGxEKBwwDAQIKAwYJAwYCkJCPAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFiMXog6oMwAAACZJREFUCNeFxiEOwCAAALEe8P8Pk5CJGRxVhUKMalusO57xZ9bhA4YjA0dmCxwoAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FWdkdfyBr5TwUufLIYBxRf%2Ff5066270eea29c7ef149ba6eb3c95528%2FiPhone_8_Plus_-_16.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FWdkdfyBr5TwUufLIYBxRf%2Ff5066270eea29c7ef149ba6eb3c95528%2FiPhone_8_Plus_-_16.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FWdkdfyBr5TwUufLIYBxRf%2Ff5066270eea29c7ef149ba6eb3c95528%2FiPhone_8_Plus_-_16.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<p class="renderers_paragraph__ebcQ5"> | |
First, a full rewrite tends to require a complete halt to | |
shipping new features. Otherwise, you run the risk of | |
chasing a moving target as the old codebase grows while you | |
try to catch up with your new code. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
A full rewrite also does not guarantee a better user | |
experience. Often, a rewrite ends up less than seamless, as | |
it's not feasible for the new version to match the old | |
one, feature for feature, edge case for edge case. As the | |
surface area of the rewrite grows, there's more room | |
for error and users can end up frustrated with breaking | |
changes and missing features. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Full rewrites also require building up an entirely new | |
codebase, which is a large quantity of unused code. In our | |
experience, unused code, even when verified with tests, can | |
be a breeding ground for bugs. We wanted to make sure that | |
any new Rust code was properly exercised as we moved through | |
our porting effort. | |
</p> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span | |
class="heading_target__nEYko" | |
id="we-chose-to-port" | |
></span | |
><a class="heading_link__sr7K9" href="#we-chose-to-port" | |
>We chose to port</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
Therefore, we decided to <b>port</b> Turborepo to Rust | |
instead of doing a full rewrite. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Porting did necessitate some tradeoffs. We had to introduce | |
a significant amount of complexity into our codebase, so | |
that we could interoperate between Go and Rust. This | |
complexity meant slower developer velocity to start, but we | |
look forward to workflow improvements going forward, | |
particularly when our porting effort has finished. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
More importantly, we knew we could continue shipping | |
features to Turborepo users while porting. All things | |
considered, we determined that this was a reasonable | |
compromise and the path that we would take. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="starting-the-port" | |
></span | |
><a class="heading_link__sr7K9" href="#starting-the-port" | |
>Starting the port </a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
We chose to start by writing a small, new Turborepo feature | |
in Rust. This way, we could add new functionality from the | |
roadmap for users, integrate Rust into our build process, | |
and interact with existing Go code as little as possible to | |
reduce our initial complexity. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Once we'd laid this groundwork, we knew that we could | |
slowly port more and more code to Rust over time. | |
</p> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span class="heading_target__nEYko" id="global-turbo"></span | |
><a class="heading_link__sr7K9" href="#global-turbo" | |
>Global | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
></a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
We decided to have our first Rust feature be | |
<a | |
href="https://turbo.build/blog/turbo-1-7-0#global-turbo?utm_source=turbo&utm_medium=blog&utm_campaign=turborepo_porting" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>global | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
></a | |
>, a feature that allows users to install Turborepo as a | |
globally available command on their machine. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
A global installation of | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
will look for a locally installed | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
program in the repository, execute it if it exists, and | |
otherwise fallback to the global | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
binary. That way, you can easily run | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
from anywhere in your repository, but also keep a specific | |
version of | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
pinned in your | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>package.json</code | |
>. | |
</p> | |
<div class="geist-show-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319413.png" | |
loading="lazy" | |
width="1650" | |
height="740" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1650 740'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAQlBMVEX+/v/9/f/////+/v749fr19vv5+vz8/P35+Pn6+fr5+fn7+/v58vb48/f5+Pr6+vr6+vv8+/z8/Pz//f7+/P39/f2gUkvnAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFhgy1biTTAAAACNJREFUCNdjZGBkZGBkYGBgZGGEABYOMPWPmYWd8R2DMFAKABQoAjAjjOa6AAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FqPT2LsrOLLG4ISEyuv92G%2F2427d2381392e4acc95dc51d1cf9edca%2FFrame_427319413.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FqPT2LsrOLLG4ISEyuv92G%2F2427d2381392e4acc95dc51d1cf9edca%2FFrame_427319413.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2FqPT2LsrOLLG4ISEyuv92G%2F2427d2381392e4acc95dc51d1cf9edca%2FFrame_427319413.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319414.png" | |
loading="lazy" | |
width="1650" | |
height="740" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1650 740'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAP1BMVEUBAQIBAgMAAAEAAAACAgIBAQEPDBIPERcFBgkDAwMHBgcFBQUGBgYTDBEVDxQHBgkFBAUGBgcHBgYCAQEDAQLqgzB0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFhgy1biTTAAAACNJREFUCNdjZGAEgf+MjGxgBuNfFnawANN3FjbG9wxMDP8ZAVCRBhRpKFCFAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2gbJI96SblT7dXxZWZDYsD%2F6877030625b5c53c3523ad96db1d468d%2FFrame_427319414.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2gbJI96SblT7dXxZWZDYsD%2F6877030625b5c53c3523ad96db1d468d%2FFrame_427319414.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2gbJI96SblT7dXxZWZDYsD%2F6877030625b5c53c3523ad96db1d468d%2FFrame_427319414.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<div class="geist-hide-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 19.png" | |
loading="lazy" | |
width="414" | |
height="647" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 647'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAANCAMAAACjHN8KAAAAgVBMVEX////+/f78/P78/f/9/v/59vv5+fz5+/71+P3+/P749Pfl5eXx8fH4+Pz9/f7//f789fn89fj79Pj69fn+/v79/P38+/v7+vv8/Pzv7+/v7/D7+/v9+/78+/38+Pz9/P79/f319fXw8PDq6ur09PT7+Pr7+fr8+/zu7u74+Pjz8/OyQWsNAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBo66tGtkgAAAFJJREFUCB0FwTEOgzAQAMFd+0AmPR0g/v8sqhSpkCWCMIIZQbURGCpNBlX3oFcVYVS/BGQVAKYZEBZ5Nlz17LI11SPf5NqCkgoWEj+Hy88fAIAXBx4QbemilmIAAAAASUVORK5CYII='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F3DzjEq5stkry2IiCm6zh4U%2Fd5f38eda9664709a42fbbc2e8707ec74%2FiPhone_8_Plus_-_19.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F3DzjEq5stkry2IiCm6zh4U%2Fd5f38eda9664709a42fbbc2e8707ec74%2FiPhone_8_Plus_-_19.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F3DzjEq5stkry2IiCm6zh4U%2Fd5f38eda9664709a42fbbc2e8707ec74%2FiPhone_8_Plus_-_19.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 5.png" | |
loading="lazy" | |
width="414" | |
height="647" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 647'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAANCAMAAACjHN8KAAAAw1BMVEUBAQECAgICAgQBAgMBAQECAQITEBUUExcRExcOEhcBAgMBAQEDAgMXExcTExcCAgMBAQECAQISCw4VDhEWDxMRDBABAQEJCQkRDxEREBIKCQkBAQEBAQEKCgocHB0dHB0BAQEFAwYBAQEHBAgEAgQDAwMWFhYcGxwJCQkICAgiISICAgIHBwcLCAsEBAQHBwcCAgIHBwcLCAsEBAQFBQULCAoCAgITExMWFhYICAgHBwcWFRYQEBADAwMBAQEpKCkbGxsbg+gnAAAAP3RSTlPOzs7O0tLk7+/k0tbW7/DW2trl7e3m3ufw8Ofd4ez29uXm6erq7vz89PT88fb39Pf1+vr39/r5/f37+/39+f3b7sc9AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBo66tGtkgAAAEVJREFUCB0FwdEVgjAABLALXoufzML+MziQr1ITCfGLAt+e3D64wNqdAaMzeI60iUh6hOydvsDancgeqwOs0QlOfSdJkj8xOg2fMOYquwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7beQhPRdj8OYxzfUcYgEbU%2Fd2e5d1a6a0d94b65231122a21672217e%2FiPhone_8_Plus_-_5.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7beQhPRdj8OYxzfUcYgEbU%2Fd2e5d1a6a0d94b65231122a21672217e%2FiPhone_8_Plus_-_5.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7beQhPRdj8OYxzfUcYgEbU%2Fd2e5d1a6a0d94b65231122a21672217e%2FiPhone_8_Plus_-_5.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<p class="renderers_paragraph__ebcQ5"> | |
This feature is implemented through what we started calling | |
"the Rust shim," a bit of Rust code that wraps the | |
existing Go code. The Go portion is compiled via CGO as a C | |
static library and then linked to the Rust binary. Luckily, | |
global | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
only required a few features from the rest of | |
Turborepo's code, such as reading configuration and | |
navigating the file system. | |
</p> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span class="heading_target__nEYko" id="cli-parsing"></span | |
><a class="heading_link__sr7K9" href="#cli-parsing" | |
>CLI parsing</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
As we implemented global | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
>, we realized we needed to parse a few command line | |
arguments like | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>--cwd</code | |
>, the argument for setting | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
>'s current working directory. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
After global | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
>, it made sense to continue by porting the rest of the CLI | |
argument parser to Rust. To parse arguments, we use the | |
<a | |
href="https://docs.rs/clap/latest/clap/" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
><code class="code_code__U8dqF" style="font-size: 0.9em" | |
>clap</code | |
> | |
crate</a | |
> | |
(Rust’s equivalent of an npm package). | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>clap</code | |
> | |
lets you define a data type with the arguments, annotate it | |
a little bit, and it will automatically create a parser. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
With the pieces in place, we had to work on sending the args | |
from the Rust entry point to the Go code. For better or | |
worse, | |
<a | |
href="https://faultlore.com/blah/c-isnt-a-language/" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>C is the standard for foreign function interfacing | |
(FFI)</a | |
>, so we had to use C to communicate between Rust and Go. | |
</p> | |
<div class="geist-show-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="InlineGraphic_1920xVariable (1).png" | |
loading="lazy" | |
width="1920" | |
height="1084" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 1084'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAANlBMVEX//v/5+fr+/v/29vb////+/v7+7uT+/f79/v79/f78/f78/P39+/305NnU7vXi9Pn+/P3y+v3IyZrdAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA6T+lWjwAAACxJREFUCB0FwYkBABAMBMHDksSv/2bNSCmXAqg2c0VHjOxz7ZCdcN2LeADwARSJAQigzNYtAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2FGPm6ksIA7bGzZzY7CYkd%2F06b7d487cbcf5237ae9f917574cf7a51%2FInlineGraphic_1920xVariable__1_.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2FGPm6ksIA7bGzZzY7CYkd%2F06b7d487cbcf5237ae9f917574cf7a51%2FInlineGraphic_1920xVariable__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2FGPm6ksIA7bGzZzY7CYkd%2F06b7d487cbcf5237ae9f917574cf7a51%2FInlineGraphic_1920xVariable__1_.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="InlineGraphic_1920xVariable.png" | |
loading="lazy" | |
width="1920" | |
height="1084" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 1084'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAAVFBMVEUDAgQODhEEBQcKCgsJCQkAAAAFAwUoFgoJCAoBAQICAgMGBQgEBggHBgkCAwQLBwoyIRgQKjIEBAYGBAURJCkJCAsDAgMLBQcJBQcCAQIIEBMEAwRuhEwzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA51uAHNQAAAClJREFUCB0FwYEJwDAMAzCLpins/z93QDqYJCDRwFerzKbrYQ6quW+SH0+iBTHcnB1jAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7sVbowOCnYSjNrOyxfD5NF%2F0fdf484cf4ced36f67e0ab879514d099%2FInlineGraphic_1920xVariable.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7sVbowOCnYSjNrOyxfD5NF%2F0fdf484cf4ced36f67e0ab879514d099%2FInlineGraphic_1920xVariable.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7sVbowOCnYSjNrOyxfD5NF%2F0fdf484cf4ced36f67e0ab879514d099%2FInlineGraphic_1920xVariable.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<div class="geist-hide-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="InlineGraphic_1920xVariable (3).png" | |
loading="lazy" | |
width="414" | |
height="416" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 416'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAdVBMVEX////v7+/u7u7+/f709Pb3+Pr19vb29vb6+fz19vn9/v/9+vz+5db+8Of5+fz8+fzZ8/rQ8Pn7+/3+/P399vb99vX8+fvw9fn9/P3+/v/9/f78/f77+v35+Pv4+fz5+/766eDw1cS03+vY8Pf9+vv9+fr8+vxuB8OQAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA51uAHNQAAADhJREFUCB0FwbENg1AQQLHnnyvoU7H/bkSiYAAkROwqq/IBzAaYjasv2nl7f1MLd6tzZkbTM3L0B7j2CCFldOOCAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1M9EpwZDGRt4PqXnS41B6p%2F0cc3e7336ee39882508a6dd112db9a5f%2FInlineGraphic_1920xVariable__3_.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1M9EpwZDGRt4PqXnS41B6p%2F0cc3e7336ee39882508a6dd112db9a5f%2FInlineGraphic_1920xVariable__3_.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1M9EpwZDGRt4PqXnS41B6p%2F0cc3e7336ee39882508a6dd112db9a5f%2FInlineGraphic_1920xVariable__3_.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="InlineGraphic_1920xVariable (2).png" | |
loading="lazy" | |
width="414" | |
height="416" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 416'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAflBMVEUAAAAQEBAREREDAgMQDxMKDBAJCgsJCQoKCQ0MDhICAwQLCAsvFwgjFg4GBwsJBQkPKC8OLjgICQ0GBAUVDA0QCw4QExgGBAYAAAEBAQIBAgMQDhIRERUPERYMEBUDAQMtGxNFKhkVP0sQJzACAgMBAAELBgkOBwkNBwoLBwo1YMUFAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA51uAHNQAAADRJREFUCB0FwbENhEAQALExrE7EH33/1ZFRAEhgV9kqO2AOwBy6+qE/ve852XA3LVhNC54+oswGVtAihkgAAAAASUVORK5CYII='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7s2Gq0JKjOQkBQ9ZAOqEGw%2F4b0db3626aa7e7b7b10b95e8fdf8d631%2FInlineGraphic_1920xVariable__2_.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7s2Gq0JKjOQkBQ9ZAOqEGw%2F4b0db3626aa7e7b7b10b95e8fdf8d631%2FInlineGraphic_1920xVariable__2_.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F7s2Gq0JKjOQkBQ9ZAOqEGw%2F4b0db3626aa7e7b7b10b95e8fdf8d631%2FInlineGraphic_1920xVariable__2_.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<p class="renderers_paragraph__ebcQ5"> | |
We wanted to avoid having too many types in C, as we weren’t | |
confident that we could write cross-platform C types that | |
played well with both Rust and Go. Instead, we decided to | |
serialize our arguments to JSON and send it to Go as a | |
string. Even though JSON serialization does have some | |
overhead, we knew that the arguments struct would only be a | |
few hundred bytes in size, so the performance impact would | |
be minimal. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
On the Rust side, we used another cornerstone crate of the | |
Rust ecosystem, | |
<a | |
href="https://docs.rs/serde/latest/serde/" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
><code class="code_code__U8dqF" style="font-size: 0.9em" | |
>serde</code | |
></a | |
>, which lets you serialize and deserialize data in various | |
different formats, using some minimal annotation. For the Go | |
side, we were already using JSON in the codebase, so it was | |
easy to receive the JSON string and deserialize it into a Go | |
struct. | |
</p> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span class="heading_target__nEYko" id="ship-it"></span | |
><a class="heading_link__sr7K9" href="#ship-it">Ship it?</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
With these two features ported, we were ready to ship our | |
first hybrid Go-Rust release. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
However, before we could release, we needed to make sure the | |
Go-Rust binary worked in all the various contexts that | |
Turborepo is used, | |
<a | |
href="https://turbo.build/repo/docs/installing?utm_source=turbo&utm_medium=blog&utm_campaign=turborepo_porting" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>like the different operating systems and Linux distros | |
that we support</a | |
>. As we tested our code, we started noticing some issues on | |
a couple platforms. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="windows-difficulties" | |
></span | |
><a class="heading_link__sr7K9" href="#windows-difficulties" | |
>Windows difficulties</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
On Windows, there are two main toolchains, | |
<a | |
href="https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>Microsoft Visual C++ (MSVC)</a | |
> | |
and | |
<a | |
href="https://en.wikipedia.org/wiki/MinGW" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>Minimalist GNU for Windows (MinGW)</a | |
>. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Go <b>only</b> uses MinGW, but we were using Rust with MSVC. | |
This caused some runtime issues, but, luckily, the solution | |
was simple: we moved our Rust toolchain to MinGW. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Next up, we had some issues with paths. Windows has a couple | |
concepts of paths, including what’s called a Universal | |
Naming Convention (UNC) path. When you ask Windows to | |
canonicalize a path (resolve all symlinks and normalize | |
components of the path), it gives you a UNC path. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
However, despite the name, UNC paths are not supported | |
everywhere—sometimes not even by Windows itself! This caused | |
a few bugs where we’d provide a UNC path and get an invalid | |
path error. The solution was to use a helpful Rust crate | |
called | |
<a | |
href="https://docs.rs/dunce/latest/dunce/" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>dunce</a | |
> | |
that lets you canonicalize a path and get a non-UNC path | |
back, handling the intricacies of this problem for us. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span class="heading_target__nEYko" id="alpine-linux"></span | |
><a class="heading_link__sr7K9" href="#alpine-linux" | |
>Alpine Linux</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
The second set of challenges came with Alpine Linux. At | |
Vercel, we use Alpine, a common operating system for cloud | |
computing, to create lightweight containers for building | |
your projects. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Alpine, though, does not come with | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>glibc</code | |
>, the de-facto implementation of the C standard library. | |
This is a problem because many binaries assume | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>glibc</code | |
> | |
is installed and don’t package it themselves. There are some | |
libraries that pave over this issue using packages like | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>gcompat</code | |
> | |
or | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>libc6-compat</code | |
>, but they didn’t end up working for us because the version | |
of | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>glibc</code | |
> | |
that Rust requires was too modern for our supported targets. | |
When we’d try to run the binary, we’d get errors that the | |
required | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>glibc</code | |
> | |
version was not available. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
As a result, we decided to compile Turborepo as a fully | |
static binary. This meant that we packaged our own C | |
standard library implementation using | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>musl</code | |
> | |
(since you can't statically link | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>glibc</code | |
> | |
due to licensing issues). This seems to work just fine for | |
both Rust and Go: Rust lets you set the C standard library | |
in the target (<code | |
class="code_code__U8dqF" | |
style="font-size: 0.9em" | |
>aarch64-unknown-linux-musl</code | |
> | |
vs. | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>aarch64-unknown-linux-gnu</code | |
>) and Go does not use a C standard library by default. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
However, when we ran this statically linked binary, it would | |
return a segmentation fault. Even worse, when we inspected | |
with a debugger, we’d find a corrupted stack. And, even | |
worser, the segfault appeared to be coming from the Go | |
runtime itself! | |
</p> | |
<hr | |
style=" | |
border: 0; | |
border-top: 1px solid var(--accents-2); | |
margin: 50px 0; | |
" | |
/> | |
<p class="renderers_paragraph__ebcQ5"> | |
After a lot of searching, we tracked down a | |
<a | |
href="https://github.com/golang/go/issues/13492" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>seven year-old GitHub issue</a | |
> | |
which explained that Go cannot be compiled as a C static | |
library with | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>musl</code | |
>. This posed a significant challenge, as Alpine Linux is an | |
essential platform for Turborepo and its users. We had to go | |
back to the drawing board and figure out how we could ship | |
our Go-Rust combination. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Eventually, after a ton more deliberation, we came up with a | |
solution: we’d compile our Go code and our Rust code as two | |
separate binaries. The Rust code would call the Go code and | |
pass the args serialized to JSON via the CLI. | |
</p> | |
<div class="geist-show-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319412.png" | |
loading="lazy" | |
width="1920" | |
height="917" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 917'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAQlBMVEX////+/v789vb79/f9/P34+Pj5+fn9/v/9/P739PX08vP8/P36+fzu8/b0+Pr8/P7+/f78/Pz5+Pn9+vv69ff69/kJxvdQAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBcXGqCPqgAAABxJREFUCNdjZAACRjBggVLcEPodCzsj4+8PQGkAFYgDDfEmFLUAAAAASUVORK5CYII='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1VOl0eTFS33pkNLbtrzhY%2F6811cbf8065443e0ba46c5c365b9fca4%2FFrame_427319412.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1VOl0eTFS33pkNLbtrzhY%2F6811cbf8065443e0ba46c5c365b9fca4%2FFrame_427319412.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F1VOl0eTFS33pkNLbtrzhY%2F6811cbf8065443e0ba46c5c365b9fca4%2FFrame_427319412.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="Frame 427319408.png" | |
loading="lazy" | |
width="1920" | |
height="917" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1920 917'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAS1BMVEUAAAAIBQkWEBITEBMKDBEDAgQLCw0ICQwBAgQMBwsbFhgdFxoQDRERDhMVGyARFxwICA4DAQMJBAYMCAoDAQIQCQ0XDxIVDhIHBQgAg+GNAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBcXGqCPqgAAABhJREFUCNdjZAACRhBgZYQAFg5kxgugNAAHNgEdXeElIwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F3KbndugMu2gM4Nu8Eoq3dj%2F41cb0b6578dbd822c77f08f2bada4066%2FFrame_427319408.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F3KbndugMu2gM4Nu8Eoq3dj%2F41cb0b6578dbd822c77f08f2bada4066%2FFrame_427319408.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F3KbndugMu2gM4Nu8Eoq3dj%2F41cb0b6578dbd822c77f08f2bada4066%2FFrame_427319408.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<div class="geist-hide-on-desktop"> | |
<div class="geist-hide-on-dark"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 14.png" | |
loading="lazy" | |
width="414" | |
height="506" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 506'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAAb1BMVEX////8/Pzy8/P09PX7+/v+/v/69/z39vz19/34+/7++/358/Tw6eXx6+j59vf//v799/r99Pj79fj8+fz29fb//v/+/f729vvy9fvx9vz4+v7+///+/P339vjr7/Ds7/D7+/z9+Pv99vn89vn8+vzLGsDAAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBcXGqCPqgAAADhJREFUCNdjYIACRiACgj8MLAysqIzfUMYnsDoRESDBwsAgygjRJQaSYnzKwsAGpoFSP3mAjN8MAF7ZCUtzYtguAAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F5QibrcV35p6zu0Rpzriwn5%2F57872cf4a380ba9c3ec780a02c370775%2FiPhone_8_Plus_-_14.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F5QibrcV35p6zu0Rpzriwn5%2F57872cf4a380ba9c3ec780a02c370775%2FiPhone_8_Plus_-_14.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F5QibrcV35p6zu0Rpzriwn5%2F57872cf4a380ba9c3ec780a02c370775%2FiPhone_8_Plus_-_14.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
<div class="geist-hide-on-light"> | |
<figure | |
class="renderers_image-wrapper__iG_Go" | |
data-vercel-edit-target="true" | |
> | |
<div | |
class="renderers_image__R8Lae" | |
style="max-width: 800px" | |
> | |
<img | |
data-version="v1" | |
alt="iPhone 8 Plus - 18.png" | |
loading="lazy" | |
width="414" | |
height="506" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style=" | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 414 506'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAAclBMVEUAAAACAgIMDA0LCwwEBAQBAAEKCAwMDBIKDBMGCQ4AAQECAQIXEBMnHx0kHRwUEhcBAgMNBgkTCA0SCA0LBgoICAgKCQoBAQIDAgQCAgQPDhQQFh8OFh8KDhUVDxMgHB4fHB4SDxQGAgQJAgUIAgUFAgRZB5BMAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFiQ1OC9/EAAAAEBJREFUCB01wTEOgDAMBMG1zyBF1JD/v5E4FUI4BTP8DKw8GJstM9htmYIm6X4p5wUIuvuRiC73aOmMKAMnoyQfg9cLcFt1VtUAAAAASUVORK5CYII='/%3E%3C/svg%3E'); | |
" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2547NHodIcPCgR7ITmjnM0%2F4366bba1f016889729cfc1c729554907%2FiPhone_8_Plus_-_18.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1x, | |
/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2547NHodIcPCgR7ITmjnM0%2F4366bba1f016889729cfc1c729554907%2FiPhone_8_Plus_-_18.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2x | |
" | |
src="/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F2547NHodIcPCgR7ITmjnM0%2F4366bba1f016889729cfc1c729554907%2FiPhone_8_Plus_-_18.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</figure> | |
</div> | |
</div> | |
<p class="renderers_paragraph__ebcQ5"> | |
We knew that the args were small enough that they could be | |
passed via CLI without too much of a performance hit. And | |
because we were using a serialization format, the code | |
changes were extremely small. All we had to do was change | |
how Rust was sending the JSON string to Go. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
With that, we were able to get our first hybrid Go-Rust | |
release out the door. The first version of | |
<code class="code_code__U8dqF" style="font-size: 0.9em" | |
>turbo</code | |
> | |
that was shipped to you using these compilation strategies | |
was | |
<a | |
href="https://turbo.build/blog/turbo-1-7-0?utm_source=turbo&utm_medium=blog&utm_campaign=turborepo_porting" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>version 1.7.0</a | |
>. | |
</p> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span | |
class="heading_target__nEYko" | |
id="what-we-learned" | |
></span | |
><a class="heading_link__sr7K9" href="#what-we-learned" | |
>What we learned</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
Through this effort, we've learned a lot about moving | |
from one language to another. Let's take note of what | |
we've found. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="serialization-is-useful-for-ffi" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#serialization-is-useful-for-ffi" | |
>Serialization is useful for FFI</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
Our first takeaway is that serialization formats are very | |
useful for interoperability. By serializing to JSON, a | |
format with robust support in both Go and Rust, we were able | |
to minimize our FFI surface area, and avoid a whole class of | |
cross-platform, cross-language bugs. When we had to switch | |
from a single, linked binary to two binaries, we were able | |
to do so with relative ease because our FFI surface area was | |
so small. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
The tradeoff here is that serialization and deserialization | |
is slow. You can only depend on this technique if either you | |
know your serialized payloads will be small or you | |
don't care about the performance hit for your use case. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="porting-takes-preparation" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#porting-takes-preparation" | |
>Porting takes preparation</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
The second takeaway is that incremental porting is feasible | |
but requires lots of careful testing and strategizing. We | |
ran into quite a few tricky bugs and we caught these issues | |
through lots of automated and manual testing. You can | |
<a | |
href="https://github.com/vercel/turbo/tree/main/.github/workflows" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>check out our (and Turbopack's) testing suites in | |
our GitHub workflows</a | |
>. | |
</p> | |
<p class="renderers_paragraph__ebcQ5"> | |
Testing is also extremely important for nailing down the | |
behavior of your code, whether it’s the exact edge cases of | |
CLI parsing, or the order in which configuration is loaded. | |
These exact details are not so crucial when you’re writing | |
your first implementation, but they’re absolutely paramount | |
to avoid breaking changes during a port or rewrite. You | |
should aim to write tests <b>before</b> you start porting | |
code, so that you have a known specification to work | |
against. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="cross-compatibility-is-difficult" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#cross-compatibility-is-difficult" | |
>Cross-compatibility is difficult</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
The third takeaway is that cross-platform, cross-language | |
release engineering is extremely challenging. Every | |
platform, language, and compiler has their own quirks that | |
can make interoperability difficult and, the more things you | |
have working together, the more opportunities you have for a | |
new complication. | |
</p> | |
<h3 class="renderers_heading3__s68C0"> | |
<span | |
class="heading_target__nEYko" | |
id="porting-is-worth-it-for-us" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#porting-is-worth-it-for-us" | |
>Porting is worth it for us</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h3> | |
<p class="renderers_paragraph__ebcQ5"> | |
Finally, while porting from Go to Rust has been challenging, | |
it has proven to be the correct choice for us strategically. | |
Even with our porting effort going on, we've been able | |
to ship new features, handle bugs in existing functionality, | |
and keep helping our users while we migrate. It's | |
required some extraordinarily tricky debugging, careful | |
planning, and rigorous testing, but we believe it has been | |
worth it. | |
</p> | |
<h2 class="renderers_heading2__9zxb_"> | |
<span | |
class="heading_target__nEYko" | |
id="try-out-(ported)-turborepo" | |
></span | |
><a | |
class="heading_link__sr7K9" | |
href="#try-out-(ported)-turborepo" | |
>Try out (ported) Turborepo</a | |
><span class="heading_permalink__6hJz8" | |
><svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style=" | |
color: currentColor; | |
width: 0.75em; | |
height: 0.75em; | |
" | |
> | |
<path | |
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" | |
/> | |
<path | |
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" | |
/></svg | |
></span> | |
</h2> | |
<p class="renderers_paragraph__ebcQ5"> | |
This week, Turborepo saved 5,742 hours of time for the | |
product engineers and CI machines at Vercel. If you want to | |
try out the same technology in just a few minutes, | |
<a | |
href="https://vercel.com/blog/vercel-remote-cache-turbo" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>check out our article</a | |
> | |
on how you can get started with | |
<a | |
href="https://vercel.com/docs/concepts/monorepos/remote-caching" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_highlight__kJZF9" | |
>Vercel Remote Cache</a | |
>. | |
</p> | |
<div class="explore-more_wrapper__7FarS"> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 1.25rem; | |
--text-line-height: 1.5rem; | |
--text-letter-spacing: -0.020625rem; | |
--text-weight: 600; | |
" | |
> | |
Explore more | |
</p> | |
<div class="explore-more_cardsGrid__P85E_"> | |
<a | |
class="link_link__hbWKh explore-more_cardLink__rZmfg" | |
href="/templates/next.js/turborepo-next-basic" | |
> | |
<div class="explore-more_card__fge_u"> | |
<div class="explore-more_cardHeader__3nZpp"> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
" | |
> | |
Deploy this template | |
</p> | |
</div> | |
<div class="explore-more_template__16d6p"> | |
<img | |
data-version="v1" | |
alt="Screenshot of template" | |
loading="lazy" | |
decoding="async" | |
data-nimg="fill" | |
style=" | |
position: absolute; | |
height: 100%; | |
width: 100%; | |
left: 0; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
object-fit: cover; | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='xMidYMid slice' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAAeFBMVEUKEB0LEiEMEiEREB4ZDBgeCRQfCBIbBxAHDBYgLj8kKjY8O0Y8MTtEMztALjUVBQwEBw0iHCkXGyUxMDk4MDcrHiQkGB0MAwcBAwUCBQkEBgsbGx8eGh0KAwcIAgQFAQMAAQEBAQIBAgMDAwUEAwQDAQICAQEBAAE3UvI7AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wMRDToISzoOqAAAABJJREFUCNdjZGCEAg4ozYKbAQAHCABStFfc+QAAAABJRU5ErkJggg=='/%3E%3C/svg%3E'); | |
" | |
sizes="25vw" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=256&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 256w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=384&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 384w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 640w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=750&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 750w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 828w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=1080&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1080w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=1200&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1200w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1920w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=2048&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2048w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 3840w | |
" | |
src="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F2JxNyYATuuV7WPuJ31kF9Q%2F433990aa4c8e7524a9095682fb08f0b1%2FBasic.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
<div class="explore-more_templateWrapper__lYg5x"> | |
<svg | |
fill="none" | |
height="20" | |
viewBox="0 0 180 180" | |
width="20" | |
xmlns="http://www.w3.org/2000/svg" | |
> | |
<g mask="url(#mask0_292_290)"> | |
<circle | |
cx="90" | |
cy="90" | |
fill="black" | |
r="87" | |
stroke="var( --next-icon-border)" | |
stroke-width="6" | |
></circle> | |
<path | |
d="M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z" | |
fill="url(#paint0_linear_292_290)" | |
></path> | |
<rect | |
fill="url(#paint1_linear_292_290)" | |
height="72" | |
width="12" | |
x="115" | |
y="54" | |
></rect> | |
</g> | |
<defs> | |
<linearGradient | |
gradientUnits="userSpaceOnUse" | |
id="paint0_linear_292_290" | |
x1="109" | |
x2="144.5" | |
y1="116.5" | |
y2="160.5" | |
> | |
<stop stop-color="white"></stop> | |
<stop | |
offset="1" | |
stop-color="white" | |
stop-opacity="0" | |
></stop> | |
</linearGradient> | |
<linearGradient | |
gradientUnits="userSpaceOnUse" | |
id="paint1_linear_292_290" | |
x1="121" | |
x2="120.799" | |
y1="54" | |
y2="106.875" | |
> | |
<stop stop-color="white"></stop> | |
<stop | |
offset="1" | |
stop-color="white" | |
stop-opacity="0" | |
></stop> | |
</linearGradient> | |
</defs> | |
</svg> | |
<p | |
class="text_wrapper__i87JK explore-more_templatePublisher__kQKUo" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
by | |
<!-- -->▲ Vercel | |
</p> | |
</div> | |
</div> | |
</div> </a | |
><a | |
href="https://turbo.build" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh explore-more_cardLink__rZmfg" | |
> | |
<div class="explore-more_card__fge_u"> | |
<div class="explore-more_cardHeader__3nZpp"> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
" | |
> | |
Visit turbo.build | |
</p> | |
</div> | |
<div class="explore-more_template__16d6p"> | |
<img | |
data-version="v1" | |
alt="Visit turbo.build" | |
loading="lazy" | |
decoding="async" | |
data-nimg="fill" | |
style=" | |
position: absolute; | |
height: 100%; | |
width: 100%; | |
left: 0; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
object-fit: cover; | |
color: transparent; | |
background-size: cover; | |
background-position: 50% 50%; | |
background-repeat: no-repeat; | |
background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='xMidYMid slice' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAMN2lDQ1BpY2MAAEiJlVcHWFPJFp5bkpBAaAEEpITeBOkEkBJCC70j2AhJgFBiDAQVO7qo4NrFAjZ0VUTBCogdsbMI9r5YEFDWxYJdeZMCuu4r35vvmzv//efMf86cO/fOHQDUTnFEolxUHYA8YYE4LiSAPjYllU7qBkRgACjAGhhxuPkiZkxMBIBlqP17eXcTINL2mr1U65/9/7Vo8Pj5XACQGIjTefncPIgPAYBXckXiAgCIUt5saoFIimEFWmIYIMSLpDhTjiulOF2O98lsEuJYELcAoKTC4YgzAVBthzy9kJsJNVT7IXYU8gRCANToEPvm5U3mQZwGsTW0EUEs1Wek/6CT+TfN9GFNDidzGMvnIitKgYJ8US5n+v+Zjv9d8nIlQz4sYVXJEofGSecM83Y7Z3K4FKtA3CdMj4qGWBPiDwKezB5ilJIlCU2U26MG3HwWzBnQgdiRxwkMh9gA4mBhblSEgk/PEASzIYYrBJ0mKGAnQKwL8SJ+flC8wmaLeHKcwhdanyFmMRX8BY5Y5lfq66EkJ5Gp0H+dxWcr9DHVoqyEZIgpEJsXCpKiIFaF2CE/Jz5cYTOmKIsVNWQjlsRJ4zeHOI4vDAmQ62OFGeLgOIV9aV7+0HyxLVkCdpQCHyjISgiV5wdr4XJk8cO5YO18ITNxSIefPzZiaC48fmCQfO5YD1+YGK/Q+SAqCIiTj8UpotwYhT1uys8NkfKmELvmF8YrxuJJBXBByvXxDFFBTII8TrwomxMWI48HXw4iAAsEAjqQwJoOJoNsIGjra+iDd/KeYMABYpAJ+MBewQyNSJb1COE1HhSBPyHig/zhcQGyXj4ohPzXYVZ+tQcZst5C2Ygc8AziPBAOcuG9RDZKOOwtCTyFjOAf3jmwcmG8ubBK+/89P8R+Z5iQiVAwkiGPdLUhS2IQMZAYSgwm2uD6uC/ujUfAqz+szjgD9xyax3d7wjNCB+Ex4Qahk3BnkqBY/FOUkaAT6gcrcpH+Yy5wS6jphgfgPlAdKuM6uD6wx12hHybuBz27QZaliFuaFfpP2n+bwQ9PQ2FHdiSj5BFkf7L1zyNVbVXdhlWkuf4xP/JY04fzzRru+dk/64fs82Ab/rMltgg7iJ3HTmMXsWNYA6BjJ7FGrBU7LsXDq+upbHUNeYuTxZMDdQT/8Df0ZKWZzHescex1/CLvK+BPk36jAWuyaLpYkJlVQGfCHYFPZwu5DqPozo7OLgBI9xf55+tNrGzfQHRav3Pz/wDA5+Tg4ODR71zYSQD2e8DX/8h3zpoBtw5lAC4c4UrEhXIOl14I8CuhBt80PWAEzOD+ZQ+cgTvwBv4gCISBaJAAUsBEGH0WXOdiMBXMBPNACSgDy8EasAFsBtvALrAXHAAN4Bg4Dc6By6Ad3AD34OrpAi9AP3gHPiMIQkKoCA3RQ4wRC8QOcUYYiC8ShEQgcUgKkoZkIkJEgsxE5iNlyEpkA7IVqUb2I0eQ08hFpAO5gzxCepHXyCcUQ1VQLdQQtURHowyUiYajCegENBOdghahC9Cl6Dq0Ct2D1qOn0cvoDbQTfYEOYABTxnQwE8weY2AsLBpLxTIwMTYbK8XKsSqsFmuCz/ka1on1YR9xIk7D6bg9XMGheCLOxafgs/El+AZ8F16Pt+DX8Ed4P/6NQCUYEOwIXgQ2YSwhkzCVUEIoJ+wgHCache9SF+EdkUjUIVoRPeC7mELMJs4gLiFuJNYRTxE7iE+IAyQSSY9kR/IhRZM4pAJSCWk9aQ/pJOkqqYv0QUlZyVjJWSlYKVVJqFSsVK60W+mE0lWlbqXPZHWyBdmLHE3mkaeTl5G3k5vIV8hd5M8UDYoVxYeSQMmmzKOso9RSzlLuU94oKyubKnsqxyoLlOcqr1Pep3xB+ZHyRxVNFVsVlsp4FYnKUpWdKqdU7qi8oVKpllR/aiq1gLqUWk09Q31I/aBKU3VQZavyVOeoVqjWq15VfalGVrNQY6pNVCtSK1c7qHZFrU+drG6pzlLnqM9Wr1A/on5LfUCDpuGkEa2Rp7FEY7fGRY0eTZKmpWaQJk9zgeY2zTOaT2gYzYzGonFp82nbaWdpXVpELSsttla2VpnWXq02rX5tTW1X7STtadoV2se1O3UwHUsdtk6uzjKdAzo3dT6NMBzBHMEfsXhE7YirI97rjtT11+XrlurW6d7Q/aRH1wvSy9Fbodeg90Af17fVj9Wfqr9J/6x+30itkd4juSNLRx4YedcANbA1iDOYYbDNoNVgwNDIMMRQZLje8Ixhn5GOkb9RttFqoxNGvcY0Y19jgfFq45PGz+nadCY9l76O3kLvNzEwCTWRmGw1aTP5bGplmmhabFpn+sCMYsYwyzBbbdZs1m9ubB5pPtO8xvyuBdmCYZFlsdbivMV7SyvLZMuFlg2WPVa6VmyrIqsaq/vWVGs/6ynWVdbXbYg2DJscm4027baorZttlm2F7RU71M7dTmC30a5jFGGU5yjhqKpRt+xV7Jn2hfY19o8cdBwiHIodGhxejjYfnTp6xejzo785ujnmOm53vOek6RTmVOzU5PTa2daZ61zhfN2F6hLsMsel0eWVq50r33WT6203mluk20K3Zrev7h7uYvda914Pc480j0qPWwwtRgxjCeOCJ8EzwHOO5zHPj17uXgVeB7z+8rb3zvHe7d0zxmoMf8z2MU98TH04Plt9On3pvmm+W3w7/Uz8OH5Vfo/9zfx5/jv8u5k2zGzmHubLAMcAccDhgPcsL9Ys1qlALDAksDSwLUgzKDFoQ9DDYNPgzOCa4P4Qt5AZIadCCaHhoStCb7EN2Vx2Nbs/zCNsVlhLuEp4fPiG8McRthHiiKZINDIsclXk/SiLKGFUQzSIZkevin4QYxUzJeZoLDE2JrYi9lmcU9zMuPPxtPhJ8bvj3yUEJCxLuJdonShJbE5SSxqfVJ30PjkweWVy59jRY2eNvZyinyJIaUwlpSal7kgdGBc0bs24rvFu40vG35xgNWHahIsT9SfmTjw+SW0SZ9LBNEJactrutC+caE4VZyCdnV6Z3s9lcddyX/D8eat5vXwf/kp+d4ZPxsqMnkyfzFWZvVl+WeVZfQKWYIPgVXZo9ubs9znROTtzBnOTc+vylPLS8o4INYU5wpbJRpOnTe4Q2YlKRJ1TvKasmdIvDhfvyEfyJ+Q3FmjBH/lWibXkF8mjQt/CisIPU5OmHpymMU04rXW67fTF07uLgot+m4HP4M5onmkyc97MR7OYs7bORmanz26eYzZnwZyuuSFzd82jzMuZ93uxY/HK4rfzk+c3LTBcMHfBk19CfqkpUS0Rl9xa6L1w8yJ8kWBR22KXxesXfyvllV4qcywrL/uyhLvk0q9Ov677dXBpxtK2Ze7LNi0nLhcuv7nCb8WulRori1Y+WRW5qn41fXXp6rdrJq25WO5avnktZa1kbee6iHWN683XL1//ZUPWhhsVARV1lQaViyvfb+RtvLrJf1PtZsPNZZs/bRFsub01ZGt9lWVV+TbitsJtz7YnbT//G+O36h36O8p2fN0p3Nm5K25XS7VHdfVug93LatAaSU3vnvF72vcG7m2sta/dWqdTV7YP7JPse74/bf/NA+EHmg8yDtYesjhUeZh2uLQeqZ9e39+Q1dDZmNLYcSTsSHOTd9Phow5Hdx4zOVZxXPv4shOUEwtODJ4sOjlwSnSq73Tm6SfNk5rvnRl75npLbEvb2fCzF84Fnztznnn+5AWfC8cuel08colxqeGy++X6VrfWw7+7/X64zb2t/orHlcZ2z/amjjEdJ676XT19LfDauevs65dvRN3ouJl48/at8bc6b/Nu99zJvfPqbuHdz/fm3ifcL32g/qD8ocHDqj9s/qjrdO88/ijwUevj+Mf3nnCfvHia//RL14Jn1Gfl3cbd1T3OPcd6g3vbn4973vVC9OJzX8mfGn9WvrR+eegv/79a+8f2d70Svxp8veSN3pudb13fNg/EDDx8l/fu8/vSD3ofdn1kfDz/KflT9+epX0hf1n21+dr0Lfzb/cG8wUERR8yR/QpgsKIZGQC83gkANQUAGjyfUcbJz3+ygsjPrDIE/hOWnxFlxR2AWvj/HtsH/25uAbBvOzx+QX218QDEUAFI8ASoi8twHTqryc6V0kKE54At0V/T89LBvynyM+cPcf/cAqmqK/i5/RdpAHx7OAgVCgAAAHhQTFRFBgcKDQYJDgUJBAICBgYHCAoPCAkOBgQGCQsQJRggMiQuBwMFBQQHKyk1HhsmDAcKCg4VEg8TEg0RBQQFAwIEDw0SEAwREwkNDRYkCQ8ZBwsSCAkMDAkLFAoOHQ0TKBEZFh0oEhcfFxkfFRUYHRkcHBMXIhMZJxEZ4sxg/AAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAAd0SU1FB+cHDRUzFQSEZRcAAAASSURBVAjXY2RghAIOKM2CmwEABwgAUrRX3PkAAAASdEVYdGV4aWY6RXhpZk9mZnNldAA3OMnUeycAAAAZdEVYdGV4aWY6UGl4ZWxYRGltZW5zaW9uADIyMDDWr4sEAAAAGXRFWHRleGlmOlBpeGVsWURpbWVuc2lvbgAxMjQ4F1ayNAAAAFx0RVh0ZXhpZjpVc2VyQ29tbWVudAA2NSwgODMsIDY3LCA3MywgNzMsIDAsIDAsIDAsIDgzLCA5OSwgMTE0LCAxMDEsIDEwMSwgMTEwLCAxMTUsIDEwNCwgMTExLCAxMTZAuB9yAAAAKHRFWHRpY2M6Y29weXJpZ2h0AENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDIzk7OPCgAAABd0RVh0aWNjOmRlc2NyaXB0aW9uAERpc3BsYXkXG5W4AAAAAElFTkSuQmCC'/%3E%3C/svg%3E'); | |
" | |
sizes="25vw" | |
srcset=" | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=256&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 256w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=384&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 384w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=640&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 640w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=750&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 750w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=828&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 828w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=1080&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1080w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=1200&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1200w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=1920&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 1920w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=2048&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 2048w, | |
/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68 3840w | |
" | |
src="/_next/image?url=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Fcontentful%2Fimage%2Fe5382hct74si%2F6AXDBERJ8a3qkQkJbDMojJ%2F47691b985c3f224b0e2742564a3a5ef1%2FCleanShot_2023-07-13_at_14.50.43_2x.png&w=3840&q=75&dpl=dpl_5H79s3kBaB3kecS1NLM5MkdsaL68" | |
/> | |
</div> | |
</div> | |
</a> | |
</div> | |
</div> | |
<div style="margin-top: 40px; margin-bottom: 10px"> | |
<div class="utils_mobileOnly__pEh2w"> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 20px; | |
" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 0.75rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
margin-bottom: -2px; | |
" | |
> | |
Related reading | |
</p> | |
<div data-vercel-edit-target="true"> | |
<a | |
class="related-reading_link__pfBti" | |
href="/blog/turborepo-migration-go-rust" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 1rem; | |
--text-line-height: 1.5rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
" | |
> | |
Why Turborepo is migrating from Go to Rust | |
</p> | |
</a> | |
<div class="related-reading_authorsWrapper__LXVGY"> | |
<div class="group_group__CHeZc"> | |
<span class="group_avatar__OEwWt" | |
><span | |
aria-label="Avatar for anthony-shew" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 20px" | |
><img | |
data-version="v1" | |
alt="Avatar for anthony-shew" | |
title="Avatar for anthony-shew" | |
loading="eager" | |
width="20" | |
height="20" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=anthony-shew&s=40" /></span></span | |
><span class="group_avatar__OEwWt" | |
><span | |
aria-label="Avatar for jared" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 20px" | |
><img | |
data-version="v1" | |
alt="Avatar for jared" | |
title="Avatar for jared" | |
loading="eager" | |
width="20" | |
height="20" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=jared&s=40" /></span | |
></span> | |
</div> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Anthony Shew, | |
<!-- -->Jared Palmer<!-- -->, and 3 others | |
</p> | |
</div> | |
</div> | |
<div data-vercel-edit-target="true"> | |
<a | |
class="related-reading_link__pfBti" | |
href="/blog/vercel-remote-cache-turbo" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 1rem; | |
--text-line-height: 1.5rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
" | |
> | |
Faster iteration with Turborepo and Vercel Remote | |
Cache | |
</p> | |
</a> | |
<div class="related-reading_authorsWrapper__LXVGY"> | |
<div class="group_group__CHeZc"> | |
<span class="group_avatar__OEwWt" | |
><span | |
aria-label="Avatar for anthony-shew" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 20px" | |
><img | |
data-version="v1" | |
alt="Avatar for anthony-shew" | |
title="Avatar for anthony-shew" | |
loading="eager" | |
width="20" | |
height="20" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=anthony-shew&s=40" /></span | |
></span> | |
</div> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Anthony Shew | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="utils_desktopOnly__RlogC"> | |
<div class="article-layout_articleSidebar__gz1bd"> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 16px; | |
" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Posted by | |
</p> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
min-height: 40px; | |
--stack-flex: initial; | |
--stack-direction: row; | |
--stack-align: center; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 12px; | |
" | |
data-vercel-edit-target="true" | |
> | |
<span | |
aria-label="Avatar for nicholaslyang" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 36px" | |
><img | |
data-version="v1" | |
alt="Avatar for nicholaslyang" | |
title="Avatar for nicholaslyang" | |
loading="eager" | |
width="36" | |
height="36" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=nicholaslyang&s=72" | |
/></span> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 2px; | |
" | |
> | |
<a | |
href="https://x.com/NicholasLYang" | |
rel="noopener noreferrer" | |
style="text-decoration: none" | |
target="_blank" | |
> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
letter-spacing: -0.01em; | |
margin-right: 4px; | |
" | |
> | |
Nicholas Yang | |
</p> | |
</a> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
min-height: 16px; | |
letter-spacing: -0.01em; | |
" | |
> | |
Turborepo Core Team | |
</p> | |
</div> | |
</div> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
min-height: 40px; | |
--stack-flex: initial; | |
--stack-direction: row; | |
--stack-align: center; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 12px; | |
" | |
data-vercel-edit-target="true" | |
> | |
<span | |
aria-label="Avatar for anthony-shew" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 36px" | |
><img | |
data-version="v1" | |
alt="Avatar for anthony-shew" | |
title="Avatar for anthony-shew" | |
loading="eager" | |
width="36" | |
height="36" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=anthony-shew&s=72" | |
/></span> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 2px; | |
" | |
> | |
<a | |
href="https://x.com/anthonysheww" | |
rel="noopener noreferrer" | |
style="text-decoration: none" | |
target="_blank" | |
> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
letter-spacing: -0.01em; | |
margin-right: 4px; | |
" | |
> | |
Anthony Shew | |
</p> | |
</a> | |
<p | |
class="text_wrapper__i87JK text_nowrap__Libwk" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
min-height: 16px; | |
letter-spacing: -0.01em; | |
" | |
> | |
Content Engineer | |
</p> | |
</div> | |
</div> | |
</div> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 20px; | |
" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 0.75rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
margin-bottom: -2px; | |
" | |
> | |
Related reading | |
</p> | |
<div data-vercel-edit-target="true"> | |
<a | |
class="related-reading_link__pfBti" | |
href="/blog/turborepo-migration-go-rust" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 1rem; | |
--text-line-height: 1.5rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
" | |
> | |
Why Turborepo is migrating from Go to Rust | |
</p> | |
</a> | |
<div class="related-reading_authorsWrapper__LXVGY"> | |
<div class="group_group__CHeZc"> | |
<span class="group_avatar__OEwWt" | |
><span | |
aria-label="Avatar for anthony-shew" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 20px" | |
><img | |
data-version="v1" | |
alt="Avatar for anthony-shew" | |
title="Avatar for anthony-shew" | |
loading="eager" | |
width="20" | |
height="20" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=anthony-shew&s=40" /></span></span | |
><span class="group_avatar__OEwWt" | |
><span | |
aria-label="Avatar for jared" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 20px" | |
><img | |
data-version="v1" | |
alt="Avatar for jared" | |
title="Avatar for jared" | |
loading="eager" | |
width="20" | |
height="20" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=jared&s=40" /></span | |
></span> | |
</div> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Anthony Shew, | |
<!-- -->Jared Palmer<!-- -->, and 3 others | |
</p> | |
</div> | |
</div> | |
<div data-vercel-edit-target="true"> | |
<a | |
class="related-reading_link__pfBti" | |
href="/blog/vercel-remote-cache-turbo" | |
> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-1000); | |
--text-size: 1rem; | |
--text-line-height: 1.5rem; | |
--text-letter-spacing: initial; | |
--text-weight: 600; | |
" | |
> | |
Faster iteration with Turborepo and Vercel Remote | |
Cache | |
</p> | |
</a> | |
<div class="related-reading_authorsWrapper__LXVGY"> | |
<div class="group_group__CHeZc"> | |
<span class="group_avatar__OEwWt" | |
><span | |
aria-label="Avatar for anthony-shew" | |
class="avatar_avatar__YoQfd" | |
data-geist-avatar="" | |
data-version="v1" | |
role="img" | |
style="--size: 20px" | |
><img | |
data-version="v1" | |
alt="Avatar for anthony-shew" | |
title="Avatar for anthony-shew" | |
loading="eager" | |
width="20" | |
height="20" | |
decoding="async" | |
data-nimg="1" | |
class="image_intrinsic__tr5le" | |
style="color: transparent" | |
src="/api/www/avatar?u=anthony-shew&s=40" /></span | |
></span> | |
</div> | |
<p | |
class="text_wrapper__i87JK" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Anthony Shew | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<div class="blog-closing_blogClosing__rCOow"> | |
<div | |
class="stack_stack__iZkUS stack footer_footer__d3IgA" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 0px; | |
" | |
> | |
<div | |
class="stack_stack__iZkUS stack footer_wrapper__mzP__ geist-wrapper" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: space-between; | |
--stack-padding: 0px; | |
--stack-gap: 40px; | |
" | |
> | |
<div | |
class="stack_stack__iZkUS stack footer_text__plF2j" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 20px; | |
" | |
> | |
<div | |
class="stack_stack__iZkUS stack footer_title__7GCi4" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: center; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 0px; | |
" | |
> | |
<span | |
class="animated-gradient-text_background__tSv9a animated-gradient-text_background-1__yGu4G" | |
style="--content:'Develop.';--padding:0.05em;--start-color:var(--develop-start-gradient);--end-color:var(--develop-end-gradient)" | |
><span | |
class="animated-gradient-text_foreground__PuOdy animated-gradient-text_foreground-1__s2FIH" | |
>Develop.</span | |
></span | |
><span | |
class="animated-gradient-text_background__tSv9a animated-gradient-text_background-2__wnP8s" | |
style="--content:'Preview.';--padding:0.05em;--start-color:var(--preview-start-gradient);--end-color:var(--preview-end-gradient)" | |
><span | |
class="animated-gradient-text_foreground__PuOdy animated-gradient-text_foreground-2__m3PnD" | |
>Preview.</span | |
></span | |
><span | |
class="animated-gradient-text_background__tSv9a animated-gradient-text_background-3__sz5ye" | |
style="--content:'Ship.';--padding:0.05em;--start-color:var(--ship-start-gradient);--end-color:var(--ship-end-gradient)" | |
><span | |
class="animated-gradient-text_foreground__PuOdy animated-gradient-text_foreground-3__KfLOT" | |
>Ship.</span | |
></span | |
> | |
</div> | |
<p | |
class="text_wrapper__i87JK footer_description__PhXNG" | |
data-version="v1" | |
style=" | |
--text-color: var(--ds-gray-900); | |
--text-size: 0.875rem; | |
--text-line-height: 1.25rem; | |
--text-letter-spacing: initial; | |
--text-weight: 400; | |
" | |
> | |
Vercel is the platform for frontend developers, providing | |
the speed and reliability innovators need to create at the | |
moment of inspiration. | |
</p> | |
</div> | |
<div | |
class="stack_stack__iZkUS stack footer_actions__NnxMA" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--stack-align: stretch; | |
--stack-justify: center; | |
--stack-padding: 0px; | |
--stack-gap: 16px; | |
" | |
> | |
<a | |
role="link" | |
tabindex="0" | |
href="/new" | |
type="submit" | |
data-testid="standard-closing/start-deploying-cta" | |
class="button_base__BjwbK reset_reset__KRyvc button_button__81573 reset_reset__KRyvc footer_button__GAdJL button_large__fuY6E button_invert__YNhnn" | |
data-geist-button="" | |
data-version="v1" | |
style="--geist-icon-size: 16px" | |
><span class="button_content__1aE1_" | |
>Start Deploying</span | |
></a | |
> | |
<div class="footer_gradient-button-wrapper__QH_SP"> | |
<span | |
aria-hidden="true" | |
class="footer_button-bg__Ad698 footer_bg-1__TgKEZ" | |
></span | |
><span | |
aria-hidden="true" | |
class="footer_button-bg__Ad698 footer_bg-2__LzEBK" | |
></span | |
><span | |
aria-hidden="true" | |
class="footer_button-bg__Ad698 footer_bg-3__eiVbf" | |
></span | |
><a | |
role="link" | |
tabindex="0" | |
href="/product-tour?thankyou" | |
target="_blank" | |
type="submit" | |
rel="noopener" | |
data-testid="standard-closing/take-a-tour-cta" | |
class="button_base__BjwbK reset_reset__KRyvc button_button__81573 reset_reset__KRyvc footer_gradient-button__PiD7F footer_button__GAdJL geist-new-themed geist-new-tertiary geist-new-tertiary-fill button_tertiary__rIu0q button_large__fuY6E button_invert__YNhnn" | |
data-geist-button="" | |
data-version="v1" | |
style="--geist-icon-size: 16px" | |
><span class="button_content__1aE1_" | |
>Tour the Product</span | |
></a | |
> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!--/$--> | |
</div> | |
<footer | |
class="footer_footer__O_b2S footer_marketingFooterContainer__48kHC" | |
data-version="v1" | |
> | |
<nav aria-label="Vercel Directory" role="navigation"> | |
<div class="footer_marketingFooter__QL4c2"> | |
<div | |
class="stack_stack__iZkUS stack footer_logotype__9r6ml" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--sm-stack-align: center; | |
--lg-stack-align: flex-start; | |
--stack-justify: flex-start; | |
--stack-padding: 0px; | |
--stack-gap: 32px; | |
" | |
> | |
<a href="/home" | |
><svg | |
aria-label="Vercel Inc." | |
fill="var(--geist-foreground)" | |
height="24" | |
role="img" | |
viewBox="0 0 283 64" | |
> | |
<path | |
d="M37 0l37 64H0L37 0zM159.6 34c0-10.3-7.6-17.5-18.5-17.5s-18.5 7.2-18.5 17.5c0 10.1 8.2 17.5 19.5 17.5 6.2 0 11.8-2.3 15.4-6.5l-6.8-3.9c-2.1 2.1-5.2 3.4-8.6 3.4-5 0-9.3-2.7-10.8-6.8l-.3-.7h28.3c.2-1 .3-2 .3-3zm-28.7-3l.2-.6c1.3-4.3 5.1-6.9 9.9-6.9 4.9 0 8.6 2.6 9.9 6.9l.2.6h-20.2zM267.3 34c0-10.3-7.6-17.5-18.5-17.5s-18.5 7.2-18.5 17.5c0 10.1 8.2 17.5 19.5 17.5 6.2 0 11.8-2.3 15.4-6.5l-6.8-3.9c-2.1 2.1-5.2 3.4-8.6 3.4-5 0-9.3-2.7-10.8-6.8l-.3-.7H267c.2-1 .3-2 .3-3zm-28.7-3l.2-.6c1.3-4.3 5.1-6.9 9.9-6.9 4.9 0 8.6 2.6 9.9 6.9l.2.6h-20.2zM219.3 28.3l6.8-3.9c-3.2-5-8.9-7.8-15.8-7.8-10.9 0-18.5 7.2-18.5 17.5s7.6 17.5 18.5 17.5c6.9 0 12.6-2.8 15.8-7.8l-6.8-3.9c-1.8 3-5 4.7-9 4.7-6.3 0-10.5-4.2-10.5-10.5s4.2-10.5 10.5-10.5c3.9 0 7.2 1.7 9 4.7zM282.3 5.6h-8v45h8v-45zM128.5 5.6h-9.2L101.7 36 84.1 5.6h-9.3L101.7 52l26.8-46.4zM185.1 25.8c.9 0 1.8.1 2.7.3v-8.5c-6.8.2-13.2 4-13.2 8.7v-8.7h-8v33h8V36.3c0-6.2 4.3-10.5 10.5-10.5z" | |
></path></svg | |
></a> | |
</div> | |
<div class="footer_group__1E48L"> | |
<input | |
aria-label="Open Navigation Menu" | |
class="footer_hidden__2YPyQ reset_visuallyHidden__C4UAP reset_visuallyHidden__RV0kP" | |
id="footer-group-:R2dsla6:" | |
type="checkbox" | |
/><label for="footer-group-:R2dsla6:"> | |
<h2 class="footer_header__l9pvw">Product</h2> | |
</label> | |
<ul class="footer_list__EoVSJ"> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/features/infrastructure" | |
>Infrastructure</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/features/previews" | |
>Previews</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/features/edge-functions" | |
>Edge Functions</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/analytics" | |
>Analytics</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/solutions/nextjs" | |
>Next.js</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/solutions/turborepo" | |
>Turbo</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/enterprise" | |
>Enterprise</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/docs/rest-api" | |
>CLI & API</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/changelog" | |
>Changelog</a | |
> | |
</li> | |
</ul> | |
</div> | |
<div class="footer_group__1E48L"> | |
<input | |
aria-label="Open Navigation Menu" | |
class="footer_hidden__2YPyQ reset_visuallyHidden__C4UAP reset_visuallyHidden__RV0kP" | |
id="footer-group-:R3dsla6:" | |
type="checkbox" | |
/><label for="footer-group-:R3dsla6:"> | |
<h2 class="footer_header__l9pvw">Explore</h2> | |
</label> | |
<ul class="footer_list__EoVSJ"> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/docs" | |
>Docs</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/pricing" | |
>Pricing</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/customers" | |
>Customers</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/integrations" | |
>Integrations</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/templates" | |
>Templates</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/resources" | |
>Resources</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/experts" | |
>Experts</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/guides" | |
>Guides</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/help" | |
>Help</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<span | |
class="link_link__hbWKh link_secondary__F1rqx" | |
role="link" | |
tabindex="0" | |
>⌘K</span | |
> | |
</li> | |
</ul> | |
</div> | |
<div class="footer_group__1E48L"> | |
<input | |
aria-label="Open Navigation Menu" | |
class="footer_hidden__2YPyQ reset_visuallyHidden__C4UAP reset_visuallyHidden__RV0kP" | |
id="footer-group-:R4dsla6:" | |
type="checkbox" | |
/><label for="footer-group-:R4dsla6:"> | |
<h2 class="footer_header__l9pvw">Company</h2> | |
</label> | |
<ul class="footer_list__EoVSJ"> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/about" | |
>About</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/blog" | |
>Blog</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/careers" | |
>Careers</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/contact" | |
>Contact Us</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
href="https://nextjs.org/conf" | |
rel="noopener" | |
target="_blank" | |
class="link_link__hbWKh link_external__0a5cc link_secondary__F1rqx" | |
>Next.js Conf<svg | |
class="link_externalIcon__uwKVa" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="2" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor; width: 1em; height: 1em" | |
> | |
<path | |
d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" | |
/> | |
<path d="M15 3h6v6" /> | |
<path d="M10 14L21 3" /></svg | |
></a> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/oss" | |
>Open Source</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/partners" | |
>Partners</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/security" | |
>Security</a | |
> | |
</li> | |
<li class="footer_item__tMf_5"> | |
<a | |
class="link_link__hbWKh link_secondary__F1rqx" | |
href="/legal/privacy-policy" | |
>Privacy Policy</a | |
> | |
</li> | |
<button | |
type="submit" | |
aria-haspopup="true" | |
aria-expanded="false" | |
aria-controls="menu-:R5sdsla6:" | |
data-testid="footer/legal-button" | |
aria-label="Menu" | |
data-geist-menu-button="" | |
data-is-open="false" | |
id="menu-button-:R5sdsla6H1:" | |
style="--geist-icon-size: 16px" | |
class="button_base__BjwbK reset_reset__KRyvc footer_legalButton__hkSj9" | |
data-geist-button="" | |
data-version="v1" | |
> | |
<span | |
class="button_content__1aE1_ button_flex__fCY56 button_center__nyfP_" | |
><span | |
class="link_link__jJm5S link_secondary__Xo2Cq" | |
style=" | |
display: flex; | |
align-items: center; | |
gap: 4px; | |
width: 100%; | |
" | |
>Legal<svg | |
class="with-icon_icon__MHUeb" | |
data-testid="geist-icon" | |
fill="none" | |
height="24" | |
shape-rendering="geometricPrecision" | |
stroke="currentColor" | |
stroke-linecap="round" | |
stroke-linejoin="round" | |
stroke-width="1.5" | |
viewBox="0 0 24 24" | |
width="24" | |
style="color: currentColor; width: 16px; height: 16px" | |
> | |
<path d="M6 9l6 6 6-6" /></svg></span | |
></span> | |
</button> | |
</ul> | |
</div> | |
</div> | |
</nav> | |
<section> | |
<div | |
class="stack_stack__iZkUS stack footer_marketingSubFooter___zGWw" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--stack-direction: column; | |
--sm-stack-align: center; | |
--lg-stack-align: stretch; | |
--md-stack-justify: center; | |
--lg-stack-justify: space-between; | |
--stack-padding: 0px; | |
--stack-gap: 16px; | |
" | |
> | |
<span class="footer_copyright__69W_S" | |
>© | |
<!-- -->2023<!-- --> | |
<!-- -->Vercel<!-- --> | |
Inc.</span | |
> | |
<div | |
class="stack_stack__iZkUS stack" | |
data-version="v1" | |
style=" | |
--stack-flex: initial; | |
--sm-stack-direction: column; | |
--lg-stack-direction: row; | |
--stack-align: center; | |
--stack-justify: space-between; | |
--stack-padding: 0px; | |
--stack-gap: 16px; | |
" | |
> | |
<span class="footer_contact__f20st"> | |
<ul class="footer_social__nmdaq"> | |
<li> | |
<a | |
aria-label="GitHub" | |
class="footer_github__C0XBp" | |
href="https://github.com/vercel" | |
rel="noopener" | |
target="_blank" | |
><svg | |
aria-label="github" | |
height="19" | |
viewBox="0 0 14 14" | |
width="19" | |
> | |
<path | |
d="M7 .175c-3.872 0-7 3.128-7 7 0 3.084 2.013 5.71 4.79 6.65.35.066.482-.153.482-.328v-1.181c-1.947.415-2.363-.941-2.363-.941-.328-.81-.787-1.028-.787-1.028-.634-.438.044-.416.044-.416.7.044 1.071.722 1.071.722.635 1.072 1.641.766 2.035.59.066-.459.24-.765.437-.94-1.553-.175-3.193-.787-3.193-3.456 0-.766.262-1.378.721-1.881-.065-.175-.306-.897.066-1.86 0 0 .59-.197 1.925.722a6.754 6.754 0 0 1 1.75-.24c.59 0 1.203.087 1.75.24 1.335-.897 1.925-.722 1.925-.722.372.963.131 1.685.066 1.86.46.48.722 1.115.722 1.88 0 2.691-1.641 3.282-3.194 3.457.24.219.481.634.481 1.29v1.926c0 .197.131.415.481.328C11.988 12.884 14 10.259 14 7.175c0-3.872-3.128-7-7-7z" | |
fill="currentColor" | |
fill-rule="nonzero" | |
></path></svg | |
></a> | |
</li> | |
<li> | |
<a | |
aria-label="X formerly known as Twitter" | |
class="footer_bird__eNkIZ" | |
href="https://x.com/vercel" | |
rel="noopener" | |
target="_blank" | |
><svg | |
aria-label="X formerly known as Twitter" | |
fill="currentColor" | |
height="16" | |
viewBox="0 0 22 20" | |
> | |
<path | |
d="M16.99 0H20.298L13.071 8.26L21.573 19.5H14.916L9.702 12.683L3.736 19.5H0.426L8.156 10.665L0 0H6.826L11.539 6.231L16.99 0ZM15.829 17.52H17.662L5.83 1.876H3.863L15.829 17.52Z" | |
></path></svg | |
></a> | |
</li> | |
</ul> | |
</span> | |
<div class="footer_theme-switcher__JPVDa"></div> | |
</div> | |
</div> | |
</section> | |
</footer> | |
</div> | |
</div> | |
<script id="__NEXT_DATA__" type="application/json"> | |
{ | |
"props": { | |
"pageProps": { | |
"blurBase64DataURLs": { | |
"https://images.ctfassets.net/e5382hct74si/6ZaHYc4GhEIKfRL2c1erpl/59f7fc484abd77f372331aec1f583d84/iPhone_8_Plus_-_6.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAilBMVEX//v/+/v/39/j29vf+///59/zl8vro9Pv4+/77+fz5+Pz3+f33+v758/nK7vfk9fr28fT78fP99/j+8Or29/z88/j99fj79fn7+Pv9+Pv87Or76uX69vr//f7++vz49/z2+P39+/z+/v7////69vvZ8fnb8ff1+P3l6eb159z49vv89Pj79Pj8+vx82KiOAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFA8e4WevVwAAADVJREFUCB0FwcENgDAMBLBzU9rswf6zIcEXIWwRvpQDMBswGzC6772vtZyBNyN5qiqZyS6SH7VHBXj74U6fAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/1b7106hvxHFzTGUpGnkJtO/33e299c2b1216be88278d462c085e32e/iPhone_8_Plus_-_2.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAkFBMVEUBAQELCwsMDAwKCA0RHigPHSYCBAcGBAcTERcQExoHCg8QCw8TJCsPCQsSCAocFRYMDRIOBwsYEBMYERUGAwYHAwUmFhMrGBMMCQwBAQEDAgMEAwUHBgsFBwwEAwYBAQEMCA0RKTITKjIHCxABAQEOBwwLCQ4BAQEGAwQRCAwQCQwFAwUOMzwqGhIlKCMzIxbky/qKAAAALHRSTlPP0NHb6+rW1+rr2+X+397+5ef19ePj9fXn6enp6urp7/H9/fH19vf8/P39/IzNUJEAAAAJcEhZcwAAITgAACE4AUWWMWAAAAAHdElNRQfnBw0UDx7hZ69XAAAAOElEQVQIHQXBQQ5AQBQFsFfzR7BzBAv3vxyxMAltkpiSaIC2nVdVHbcdUAsYX80BqR6rR6rFq4/8xz8HnPZXm+EAAAAASUVORK5CYII=", | |
"https://images.ctfassets.net/e5382hct74si/mZz2Um8re3MZXucpSZdoV/890469916054b2f0b364c13514e825b4/Frame_427319409.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAS1BMVEX////0+v38/Pz9/f79/v7+/f7Z8fjk9fr++vn99fb98uz9/v/++/z9/P399/X99vT//v/+/v/y+Pzx+P3+/v7a7fLo7Or99fP99vPNGza/AAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFA8e4WevVwAAAC1JREFUCB0FwTEKgDAAALFL1U0E///IDq6lmEhiuRB0+3pQL3t21sGuUWuMWT+RPwcYUMRZIAAAAABJRU5ErkJggg==", | |
"https://images.ctfassets.net/e5382hct74si/c17bo47fBBuBQztRawrOp/e16450ef2ef20d0c7bf2dc709912fc1b/Frame_427319402__1_.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAaVBMVEUDAgMLEBYIERcEBQcGBQYMCg8JDBEBAwUGAwUPJy8PICcKBggLBQYbEhMjFxEEBAgEAQMSCg4RCg4DAgQaDQ0aDg4EAgQAAAABAAEEAwUMERgJEhkCAwYHAwcXKC0gIyIFAgMbDQ0ZDg6TTIBOAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFA8e4WevVwAAAClJREFUCB0FwcENwCAQA7C4jVT2X5Vv0WELwAJ04ZR+4J8n03ZPmrw0uXVZBkK3hzI4AAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/365RREyHsEytwZlyGKa985/24ca1793a87a50ac37bdfea84c2964a5/iPhone_8_Plus_-_11.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAJCAMAAAA4jZ0cAAAAWlBMVEX////x8fHw8PD9/P7u7fLs7/T8/f/59PrS8fjT8fn09/z78/jU8fn39fr+/P388/f9/P39+/7v7vPu8Pb7/P/+7eL+7+X09vz97eX97ub49fr99vn89vr+/f5o6E+ZAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBQCXFA4ggAAADFJREFUCB0FwbERgEAAAyDyybn/tBYWKkAOQpM8xpI8xt6GQ6+vjGbhcLdlrAmjTfgB9MQFSOp6R8wAAAAASUVORK5CYII=", | |
"https://images.ctfassets.net/e5382hct74si/WdkdfyBr5TwUufLIYBxRf/f5066270eea29c7ef149ba6eb3c95528/iPhone_8_Plus_-_16.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAJCAMAAAA4jZ0cAAAAYFBMVEUAAAAQEBADAgQSERcPExkBAwUNCA8PLTUPLTYHCxEPBw0PLDQPKzQLCQ4EAQMPBgoOBgsDAgMDAgUTEhgQExkCAwYMBw0uGw8sGxAHCg8NBgotGxEKBwwDAQIKAwYJAwYCkJCPAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFiMXog6oMwAAACZJREFUCNeFxiEOwCAAALEe8P8Pk5CJGRxVhUKMalusO57xZ9bhA4YjA0dmCxwoAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/7nb5vSuSQZXEKsUUsYGNVl/f0f6be9ab08484fb087d5583d5720278/Frame_427319410.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAMAAACZFr56AAAAM1BMVEX////29vf3+Pr6+vr5+fn+/v7S7/fW8fn9/f/+/f7+8Of+7uP//v/o9Pnq9fr+9vH+9O70/J92AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBQCXFA4ggAAACBJREFUCB1jZGBkZGRgZPjDwsoIBn9Y2BkZPzHwMzIAACNfAxyhf+CXAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/7bF6sXF2wD092FiiuBXyGV/f5f57209b1f25030cb3e106a2a6189d1/Frame_427319404__1_.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAMAAACZFr56AAAARVBMVEUCAQMPDhMKDRIGBwgIBwgODRIMDhQBAgMHAwgPLDUNKTIEBQkIAwgpGREqGxEGAgUSGiERGCAEAgQFAgQhEQ8iEg8FAwVv/dKEAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBQCXFA4ggAAABRJREFUCNdjZGCEAg4I9ZeFHSoAAAwVASfKYVlVAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/3DzjEq5stkry2IiCm6zh4U/d5f38eda9664709a42fbbc2e8707ec74/iPhone_8_Plus_-_19.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAANCAMAAACjHN8KAAAAgVBMVEX////+/f78/P78/f/9/v/59vv5+fz5+/71+P3+/P749Pfl5eXx8fH4+Pz9/f7//f789fn89fj79Pj69fn+/v79/P38+/v7+vv8/Pzv7+/v7/D7+/v9+/78+/38+Pz9/P79/f319fXw8PDq6ur09PT7+Pr7+fr8+/zu7u74+Pjz8/OyQWsNAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBo66tGtkgAAAFJJREFUCB0FwTEOgzAQAMFd+0AmPR0g/v8sqhSpkCWCMIIZQbURGCpNBlX3oFcVYVS/BGQVAKYZEBZ5Nlz17LI11SPf5NqCkgoWEj+Hy88fAIAXBx4QbemilmIAAAAASUVORK5CYII=", | |
"https://images.ctfassets.net/e5382hct74si/7beQhPRdj8OYxzfUcYgEbU/d2e5d1a6a0d94b65231122a21672217e/iPhone_8_Plus_-_5.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAANCAMAAACjHN8KAAAAw1BMVEUBAQECAgICAgQBAgMBAQECAQITEBUUExcRExcOEhcBAgMBAQEDAgMXExcTExcCAgMBAQECAQISCw4VDhEWDxMRDBABAQEJCQkRDxEREBIKCQkBAQEBAQEKCgocHB0dHB0BAQEFAwYBAQEHBAgEAgQDAwMWFhYcGxwJCQkICAgiISICAgIHBwcLCAsEBAQHBwcCAgIHBwcLCAsEBAQFBQULCAoCAgITExMWFhYICAgHBwcWFRYQEBADAwMBAQEpKCkbGxsbg+gnAAAAP3RSTlPOzs7O0tLk7+/k0tbW7/DW2trl7e3m3ufw8Ofd4ez29uXm6erq7vz89PT88fb39Pf1+vr39/r5/f37+/39+f3b7sc9AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBo66tGtkgAAAEVJREFUCB0FwdEVgjAABLALXoufzML+MziQr1ITCfGLAt+e3D64wNqdAaMzeI60iUh6hOydvsDancgeqwOs0QlOfSdJkj8xOg2fMOYquwAAAABJRU5ErkJggg==", | |
"https://images.ctfassets.net/e5382hct74si/qPT2LsrOLLG4ISEyuv92G/2427d2381392e4acc95dc51d1cf9edca/Frame_427319413.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAQlBMVEX+/v/9/f/////+/v749fr19vv5+vz8/P35+Pn6+fr5+fn7+/v58vb48/f5+Pr6+vr6+vv8+/z8/Pz//f7+/P39/f2gUkvnAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFhgy1biTTAAAACNJREFUCNdjZGBkZGBkYGBgZGGEABYOMPWPmYWd8R2DMFAKABQoAjAjjOa6AAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/2gbJI96SblT7dXxZWZDYsD/6877030625b5c53c3523ad96db1d468d/Frame_427319414.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAP1BMVEUBAQIBAgMAAAEAAAACAgIBAQEPDBIPERcFBgkDAwMHBgcFBQUGBgYTDBEVDxQHBgkFBAUGBgcHBgYCAQEDAQLqgzB0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFhgy1biTTAAAACNJREFUCNdjZGAEgf+MjGxgBuNfFnawANN3FjbG9wxMDP8ZAVCRBhRpKFCFAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/1M9EpwZDGRt4PqXnS41B6p/0cc3e7336ee39882508a6dd112db9a5f/InlineGraphic_1920xVariable__3_.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAdVBMVEX////v7+/u7u7+/f709Pb3+Pr19vb29vb6+fz19vn9/v/9+vz+5db+8Of5+fz8+fzZ8/rQ8Pn7+/3+/P399vb99vX8+fvw9fn9/P3+/v/9/f78/f77+v35+Pv4+fz5+/766eDw1cS03+vY8Pf9+vv9+fr8+vxuB8OQAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA51uAHNQAAADhJREFUCB0FwbENg1AQQLHnnyvoU7H/bkSiYAAkROwqq/IBzAaYjasv2nl7f1MLd6tzZkbTM3L0B7j2CCFldOOCAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/7s2Gq0JKjOQkBQ9ZAOqEGw/4b0db3626aa7e7b7b10b95e8fdf8d631/InlineGraphic_1920xVariable__2_.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAflBMVEUAAAAQEBAREREDAgMQDxMKDBAJCgsJCQoKCQ0MDhICAwQLCAsvFwgjFg4GBwsJBQkPKC8OLjgICQ0GBAUVDA0QCw4QExgGBAYAAAEBAQIBAgMQDhIRERUPERYMEBUDAQMtGxNFKhkVP0sQJzACAgMBAAELBgkOBwkNBwoLBwo1YMUFAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA51uAHNQAAADRJREFUCB0FwbENhEAQALExrE7EH33/1ZFRAEhgV9kqO2AOwBy6+qE/ve852XA3LVhNC54+oswGVtAihkgAAAAASUVORK5CYII=", | |
"https://images.ctfassets.net/e5382hct74si/2FGPm6ksIA7bGzZzY7CYkd/06b7d487cbcf5237ae9f917574cf7a51/InlineGraphic_1920xVariable__1_.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAANlBMVEX//v/5+fr+/v/29vb////+/v7+7uT+/f79/v79/f78/f78/P39+/305NnU7vXi9Pn+/P3y+v3IyZrdAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA6T+lWjwAAACxJREFUCB0FwYkBABAMBMHDksSv/2bNSCmXAqg2c0VHjOxz7ZCdcN2LeADwARSJAQigzNYtAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/7sVbowOCnYSjNrOyxfD5NF/0fdf484cf4ced36f67e0ab879514d099/InlineGraphic_1920xVariable.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAAVFBMVEUDAgQODhEEBQcKCgsJCQkAAAAFAwUoFgoJCAoBAQICAgMGBQgEBggHBgkCAwQLBwoyIRgQKjIEBAYGBAURJCkJCAsDAgMLBQcJBQcCAQIIEBMEAwRuhEwzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcVCCA51uAHNQAAAClJREFUCB0FwYEJwDAMAzCLpins/z93QDqYJCDRwFerzKbrYQ6quW+SH0+iBTHcnB1jAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/5QibrcV35p6zu0Rpzriwn5/57872cf4a380ba9c3ec780a02c370775/iPhone_8_Plus_-_14.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAAb1BMVEX////8/Pzy8/P09PX7+/v+/v/69/z39vz19/34+/7++/358/Tw6eXx6+j59vf//v799/r99Pj79fj8+fz29fb//v/+/f729vvy9fvx9vz4+v7+///+/P339vjr7/Ds7/D7+/z9+Pv99vn89vn8+vzLGsDAAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBcXGqCPqgAAADhJREFUCNdjYIACRiACgj8MLAysqIzfUMYnsDoRESDBwsAgygjRJQaSYnzKwsAGpoFSP3mAjN8MAF7ZCUtzYtguAAAAAElFTkSuQmCC", | |
"https://images.ctfassets.net/e5382hct74si/2547NHodIcPCgR7ITmjnM0/4366bba1f016889729cfc1c729554907/iPhone_8_Plus_-_18.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAAclBMVEUAAAACAgIMDA0LCwwEBAQBAAEKCAwMDBIKDBMGCQ4AAQECAQIXEBMnHx0kHRwUEhcBAgMNBgkTCA0SCA0LBgoICAgKCQoBAQIDAgQCAgQPDhQQFh8OFh8KDhUVDxMgHB4fHB4SDxQGAgQJAgUIAgUFAgRZB5BMAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFiQ1OC9/EAAAAEBJREFUCB01wTEOgDAMBMG1zyBF1JD/v5E4FUI4BTP8DKw8GJstM9htmYIm6X4p5wUIuvuRiC73aOmMKAMnoyQfg9cLcFt1VtUAAAAASUVORK5CYII=", | |
"https://images.ctfassets.net/e5382hct74si/1VOl0eTFS33pkNLbtrzhY/6811cbf8065443e0ba46c5c365b9fca4/Frame_427319412.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAQlBMVEX////+/v789vb79/f9/P34+Pj5+fn9/v/9/P739PX08vP8/P36+fzu8/b0+Pr8/P7+/f78/Pz5+Pn9+vv69ff69/kJxvdQAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBcXGqCPqgAAABxJREFUCNdjZAACRjBggVLcEPodCzsj4+8PQGkAFYgDDfEmFLUAAAAASUVORK5CYII=", | |
"https://images.ctfassets.net/e5382hct74si/3KbndugMu2gM4Nu8Eoq3dj/41cb0b6578dbd822c77f08f2bada4066/Frame_427319408.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAS1BMVEUAAAAIBQkWEBITEBMKDBEDAgQLCw0ICQwBAgQMBwsbFhgdFxoQDRERDhMVGyARFxwICA4DAQMJBAYMCAoDAQIQCQ0XDxIVDhIHBQgAg+GNAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wcNFBcXGqCPqgAAABhJREFUCNdjZAACRhBgZYQAFg5kxgugNAAHNgEdXeElIwAAAABJRU5ErkJggg==", | |
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/7Ig7FQeMzQ5f0E5f78Qjjp/55ab8aec8f2dd11481288e624108785f/OG_Image_____Template.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAYFBMVEUPGSQQFB4XDxgoEBo6FCNIGS1PGjNOGjUYL0QQRl0MPlISHCceDhdoLxuLPSZSHTsgMUoVTmwPR18NGSQTDBReJBV3Kh9JGzcjLkokKkYgITobGSwYEiEdEBwnEB8tECOoD8MqAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFAcSIAhpdAAAABJJREFUCNdjZGCEAg4ozYLBAAAEUQA/unUekQAAAABJRU5ErkJggg==", | |
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/41iFVCHuBs1rQn4jTfRGIt/3bd01ff806eb9ab64b31445dad31cd6c/OG_Image_____Template__1_.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAAAYFBMVEXm8v/t8f316/b73+v+0uP/yN//wd//wuLM5f2j2vSr3fLh6vb96vT8v637qJT/wuXO4f+Z0/Og2PLi7vv68/vzt6nvn5b/yerS3//X3v/h4v/r6P/z7P356vn+4vT/3PP2FRcUAAAACXBIWXMAACE4AAAhOAFFljFgAAAAB3RJTUUH5wcNFAcRuQE4zgAAABJJREFUCNdjZGCEAg4ozYLBAAAEUQA/unUekQAAAABJRU5ErkJggg==", | |
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6E56Ftg7W5wsRF8rjgjS7N/42f14e2661f473af626111a517df547e/Turbo_OG.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAMAAACEE47CAAABc2lDQ1BpY2MAACiRdZHfK4NRGMc/hiZmFMqFi6VxtWmmFjfKllCSZspws732bmo/3t53ktwqtytK3Ph1wV/ArXKtFJGSW66JG/R6XlttyZ7Tc57P+Z7zPJ3zHLBF0krGqPNBJpvXw2NB11x03mV/xk4HTppwxhRDG5menqSqfdxRY8Ubr1Wr+rl/rWkpYShQ0yA8rGh6XnhceHI1r1m8LdyupGJLwqfCHl0uKHxr6fEiv1icLPKXxXokHAJbq7ArWcHxClZSekZYXo47k15RSvexXuJIZGdnJHaLd2EQZowgLiYYJUSAfoZkDuDFT5+sqJLv+82fIie5iswaa+gskyRFHo+oK1I9IVEVPSEjzZrV/799NdQBf7G6Iwj1T6b51gP2LfgumObnoWl+H0HtI1xky/m5Axh8F71Q1tz70LIBZ5dlLb4D55vQ+aDF9NivVCtuU1V4PYHmKLRdQ+NCsWelfY7vIbIuX3UFu3vQK+dbFn8ADbhnvdnSufYAAABgUExURQ8ZJBAUHhgPGCgQGzoUI0gZLU8bNE4aNhgvRBBGXQw+UhIcJx4OF2gvG4s9JlIdPCAxShVPbA9HXw0ZJBMMFF4kFXcrH0kbNyMuSiQqRiAhOhsZLBgTIR0QHSgQHy0QI8j604QAAAAJcEhZcwAAITgAACE4AUWWMWAAAAAHdElNRQfnBxUODgcYC28AAAAAEklEQVQI12NkYIQCDijNgsEAAARRAD+6dR6RAAAAJnRFWHRpY2M6Y29weXJpZ2h0AE5vIGNvcHlyaWdodCwgdXNlIGZyZWVseaea8IIAAAAhdEVYdGljYzpkZXNjcmlwdGlvbgBzUkdCIElFQzYxOTY2LTIuMVet2kcAAAAASUVORK5CYII=", | |
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/2JxNyYATuuV7WPuJ31kF9Q/433990aa4c8e7524a9095682fb08f0b1/Basic.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAAeFBMVEUKEB0LEiEMEiEREB4ZDBgeCRQfCBIbBxAHDBYgLj8kKjY8O0Y8MTtEMztALjUVBQwEBw0iHCkXGyUxMDk4MDcrHiQkGB0MAwcBAwUCBQkEBgsbGx8eGh0KAwcIAgQFAQMAAQEBAQIBAgMDAwUEAwQDAQICAQEBAAE3UvI7AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5wMRDToISzoOqAAAABJJREFUCNdjZGCEAg4ozYKbAQAHCABStFfc+QAAAABJRU5ErkJggg==", | |
"https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6AXDBERJ8a3qkQkJbDMojJ/47691b985c3f224b0e2742564a3a5ef1/CleanShot_2023-07-13_at_14.50.43_2x.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAMAAABPT11nAAAMN2lDQ1BpY2MAAEiJlVcHWFPJFp5bkpBAaAEEpITeBOkEkBJCC70j2AhJgFBiDAQVO7qo4NrFAjZ0VUTBCogdsbMI9r5YEFDWxYJdeZMCuu4r35vvmzv//efMf86cO/fOHQDUTnFEolxUHYA8YYE4LiSAPjYllU7qBkRgACjAGhhxuPkiZkxMBIBlqP17eXcTINL2mr1U65/9/7Vo8Pj5XACQGIjTefncPIgPAYBXckXiAgCIUt5saoFIimEFWmIYIMSLpDhTjiulOF2O98lsEuJYELcAoKTC4YgzAVBthzy9kJsJNVT7IXYU8gRCANToEPvm5U3mQZwGsTW0EUEs1Wek/6CT+TfN9GFNDidzGMvnIitKgYJ8US5n+v+Zjv9d8nIlQz4sYVXJEofGSecM83Y7Z3K4FKtA3CdMj4qGWBPiDwKezB5ilJIlCU2U26MG3HwWzBnQgdiRxwkMh9gA4mBhblSEgk/PEASzIYYrBJ0mKGAnQKwL8SJ+flC8wmaLeHKcwhdanyFmMRX8BY5Y5lfq66EkJ5Gp0H+dxWcr9DHVoqyEZIgpEJsXCpKiIFaF2CE/Jz5cYTOmKIsVNWQjlsRJ4zeHOI4vDAmQ62OFGeLgOIV9aV7+0HyxLVkCdpQCHyjISgiV5wdr4XJk8cO5YO18ITNxSIefPzZiaC48fmCQfO5YD1+YGK/Q+SAqCIiTj8UpotwYhT1uys8NkfKmELvmF8YrxuJJBXBByvXxDFFBTII8TrwomxMWI48HXw4iAAsEAjqQwJoOJoNsIGjra+iDd/KeYMABYpAJ+MBewQyNSJb1COE1HhSBPyHig/zhcQGyXj4ohPzXYVZ+tQcZst5C2Ygc8AziPBAOcuG9RDZKOOwtCTyFjOAf3jmwcmG8ubBK+/89P8R+Z5iQiVAwkiGPdLUhS2IQMZAYSgwm2uD6uC/ujUfAqz+szjgD9xyax3d7wjNCB+Ex4Qahk3BnkqBY/FOUkaAT6gcrcpH+Yy5wS6jphgfgPlAdKuM6uD6wx12hHybuBz27QZaliFuaFfpP2n+bwQ9PQ2FHdiSj5BFkf7L1zyNVbVXdhlWkuf4xP/JY04fzzRru+dk/64fs82Ab/rMltgg7iJ3HTmMXsWNYA6BjJ7FGrBU7LsXDq+upbHUNeYuTxZMDdQT/8Df0ZKWZzHescex1/CLvK+BPk36jAWuyaLpYkJlVQGfCHYFPZwu5DqPozo7OLgBI9xf55+tNrGzfQHRav3Pz/wDA5+Tg4ODR71zYSQD2e8DX/8h3zpoBtw5lAC4c4UrEhXIOl14I8CuhBt80PWAEzOD+ZQ+cgTvwBv4gCISBaJAAUsBEGH0WXOdiMBXMBPNACSgDy8EasAFsBtvALrAXHAAN4Bg4Dc6By6Ad3AD34OrpAi9AP3gHPiMIQkKoCA3RQ4wRC8QOcUYYiC8ShEQgcUgKkoZkIkJEgsxE5iNlyEpkA7IVqUb2I0eQ08hFpAO5gzxCepHXyCcUQ1VQLdQQtURHowyUiYajCegENBOdghahC9Cl6Dq0Ct2D1qOn0cvoDbQTfYEOYABTxnQwE8weY2AsLBpLxTIwMTYbK8XKsSqsFmuCz/ka1on1YR9xIk7D6bg9XMGheCLOxafgs/El+AZ8F16Pt+DX8Ed4P/6NQCUYEOwIXgQ2YSwhkzCVUEIoJ+wgHCache9SF+EdkUjUIVoRPeC7mELMJs4gLiFuJNYRTxE7iE+IAyQSSY9kR/IhRZM4pAJSCWk9aQ/pJOkqqYv0QUlZyVjJWSlYKVVJqFSsVK60W+mE0lWlbqXPZHWyBdmLHE3mkaeTl5G3k5vIV8hd5M8UDYoVxYeSQMmmzKOso9RSzlLuU94oKyubKnsqxyoLlOcqr1Pep3xB+ZHyRxVNFVsVlsp4FYnKUpWdKqdU7qi8oVKpllR/aiq1gLqUWk09Q31I/aBKU3VQZavyVOeoVqjWq15VfalGVrNQY6pNVCtSK1c7qHZFrU+drG6pzlLnqM9Wr1A/on5LfUCDpuGkEa2Rp7FEY7fGRY0eTZKmpWaQJk9zgeY2zTOaT2gYzYzGonFp82nbaWdpXVpELSsttla2VpnWXq02rX5tTW1X7STtadoV2se1O3UwHUsdtk6uzjKdAzo3dT6NMBzBHMEfsXhE7YirI97rjtT11+XrlurW6d7Q/aRH1wvSy9Fbodeg90Af17fVj9Wfqr9J/6x+30itkd4juSNLRx4YedcANbA1iDOYYbDNoNVgwNDIMMRQZLje8Ixhn5GOkb9RttFqoxNGvcY0Y19jgfFq45PGz+nadCY9l76O3kLvNzEwCTWRmGw1aTP5bGplmmhabFpn+sCMYsYwyzBbbdZs1m9ubB5pPtO8xvyuBdmCYZFlsdbivMV7SyvLZMuFlg2WPVa6VmyrIqsaq/vWVGs/6ynWVdbXbYg2DJscm4027baorZttlm2F7RU71M7dTmC30a5jFGGU5yjhqKpRt+xV7Jn2hfY19o8cdBwiHIodGhxejjYfnTp6xejzo785ujnmOm53vOek6RTmVOzU5PTa2daZ61zhfN2F6hLsMsel0eWVq50r33WT6203mluk20K3Zrev7h7uYvda914Pc480j0qPWwwtRgxjCeOCJ8EzwHOO5zHPj17uXgVeB7z+8rb3zvHe7d0zxmoMf8z2MU98TH04Plt9On3pvmm+W3w7/Uz8OH5Vfo/9zfx5/jv8u5k2zGzmHubLAMcAccDhgPcsL9Ys1qlALDAksDSwLUgzKDFoQ9DDYNPgzOCa4P4Qt5AZIadCCaHhoStCb7EN2Vx2Nbs/zCNsVlhLuEp4fPiG8McRthHiiKZINDIsclXk/SiLKGFUQzSIZkevin4QYxUzJeZoLDE2JrYi9lmcU9zMuPPxtPhJ8bvj3yUEJCxLuJdonShJbE5SSxqfVJ30PjkweWVy59jRY2eNvZyinyJIaUwlpSal7kgdGBc0bs24rvFu40vG35xgNWHahIsT9SfmTjw+SW0SZ9LBNEJactrutC+caE4VZyCdnV6Z3s9lcddyX/D8eat5vXwf/kp+d4ZPxsqMnkyfzFWZvVl+WeVZfQKWYIPgVXZo9ubs9znROTtzBnOTc+vylPLS8o4INYU5wpbJRpOnTe4Q2YlKRJ1TvKasmdIvDhfvyEfyJ+Q3FmjBH/lWibXkF8mjQt/CisIPU5OmHpymMU04rXW67fTF07uLgot+m4HP4M5onmkyc97MR7OYs7bORmanz26eYzZnwZyuuSFzd82jzMuZ93uxY/HK4rfzk+c3LTBcMHfBk19CfqkpUS0Rl9xa6L1w8yJ8kWBR22KXxesXfyvllV4qcywrL/uyhLvk0q9Ov677dXBpxtK2Ze7LNi0nLhcuv7nCb8WulRori1Y+WRW5qn41fXXp6rdrJq25WO5avnktZa1kbee6iHWN683XL1//ZUPWhhsVARV1lQaViyvfb+RtvLrJf1PtZsPNZZs/bRFsub01ZGt9lWVV+TbitsJtz7YnbT//G+O36h36O8p2fN0p3Nm5K25XS7VHdfVug93LatAaSU3vnvF72vcG7m2sta/dWqdTV7YP7JPse74/bf/NA+EHmg8yDtYesjhUeZh2uLQeqZ9e39+Q1dDZmNLYcSTsSHOTd9Phow5Hdx4zOVZxXPv4shOUEwtODJ4sOjlwSnSq73Tm6SfNk5rvnRl75npLbEvb2fCzF84Fnztznnn+5AWfC8cuel08colxqeGy++X6VrfWw7+7/X64zb2t/orHlcZ2z/amjjEdJ676XT19LfDauevs65dvRN3ouJl48/at8bc6b/Nu99zJvfPqbuHdz/fm3ifcL32g/qD8ocHDqj9s/qjrdO88/ijwUevj+Mf3nnCfvHia//RL14Jn1Gfl3cbd1T3OPcd6g3vbn4973vVC9OJzX8mfGn9WvrR+eegv/79a+8f2d70Svxp8veSN3pudb13fNg/EDDx8l/fu8/vSD3ofdn1kfDz/KflT9+epX0hf1n21+dr0Lfzb/cG8wUERR8yR/QpgsKIZGQC83gkANQUAGjyfUcbJz3+ygsjPrDIE/hOWnxFlxR2AWvj/HtsH/25uAbBvOzx+QX218QDEUAFI8ASoi8twHTqryc6V0kKE54At0V/T89LBvynyM+cPcf/cAqmqK/i5/RdpAHx7OAgVCgAAAHhQTFRFBgcKDQYJDgUJBAICBgYHCAoPCAkOBgQGCQsQJRggMiQuBwMFBQQHKyk1HhsmDAcKCg4VEg8TEg0RBQQFAwIEDw0SEAwREwkNDRYkCQ8ZBwsSCAkMDAkLFAoOHQ0TKBEZFh0oEhcfFxkfFRUYHRkcHBMXIhMZJxEZ4sxg/AAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAAd0SU1FB+cHDRUzFQSEZRcAAAASSURBVAjXY2RghAIOKM2CmwEABwgAUrRX3PkAAAASdEVYdGV4aWY6RXhpZk9mZnNldAA3OMnUeycAAAAZdEVYdGV4aWY6UGl4ZWxYRGltZW5zaW9uADIyMDDWr4sEAAAAGXRFWHRleGlmOlBpeGVsWURpbWVuc2lvbgAxMjQ4F1ayNAAAAFx0RVh0ZXhpZjpVc2VyQ29tbWVudAA2NSwgODMsIDY3LCA3MywgNzMsIDAsIDAsIDAsIDgzLCA5OSwgMTE0LCAxMDEsIDEwMSwgMTEwLCAxMTUsIDEwNCwgMTExLCAxMTZAuB9yAAAAKHRFWHRpY2M6Y29weXJpZ2h0AENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDIzk7OPCgAAABd0RVh0aWNjOmRlc2NyaXB0aW9uAERpc3BsYXkXG5W4AAAAAElFTkSuQmCC" | |
}, | |
"post": { | |
"content": { | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Port vs. full rewrite", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "When we were planning our migration, we briefly considered a full, ground-up rewrite. But, talking the idea through, we realized it wouldn't fit our goals as well as an incremental port would.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "What is an incremental port?", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Incremental porting moves code piece-by-piece, running new and old code together at the same time. The goal for the chunk of code being moved is to keep the behavior exactly the same as before it was ported.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": { | |
"target": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "3xNJFdTKF4N6aewlliP6AJ", | |
"type": "Entry", | |
"createdAt": "2023-07-13T20:15:26.245Z", | |
"updatedAt": "2023-07-13T20:15:26.245Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"contentType": { | |
"sys": { | |
"type": "Link", | |
"linkType": "ContentType", | |
"id": "mobileAndModeDependentMedia" | |
} | |
}, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo porting example", | |
"lightMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "6ZaHYc4GhEIKfRL2c1erpl", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:13:14.088Z", | |
"updatedAt": "2023-07-13T20:13:14.088Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo porting (light) (mobile)", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/6ZaHYc4GhEIKfRL2c1erpl/59f7fc484abd77f372331aec1f583d84/iPhone_8_Plus_-_6.png", | |
"details": { | |
"size": 255048, | |
"image": { "width": 1242, "height": 1257 } | |
}, | |
"fileName": "iPhone 8 Plus - 6.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "1b7106hvxHFzTGUpGnkJtO", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:13:54.006Z", | |
"updatedAt": "2023-07-13T20:13:54.006Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo porting (dark) (mobile)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/1b7106hvxHFzTGUpGnkJtO/33e299c2b1216be88278d462c085e32e/iPhone_8_Plus_-_2.png", | |
"details": { | |
"size": 1046250, | |
"image": { "width": 1242, "height": 1257 } | |
}, | |
"fileName": "iPhone 8 Plus - 2.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"lightDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "mZz2Um8re3MZXucpSZdoV", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:14:35.034Z", | |
"updatedAt": "2023-07-13T20:14:35.034Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo porting (desktop) (light)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/mZz2Um8re3MZXucpSZdoV/890469916054b2f0b364c13514e825b4/Frame_427319409.png", | |
"details": { | |
"size": 1371025, | |
"image": { "width": 5760, "height": 4227 } | |
}, | |
"fileName": "Frame 427319409.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "c17bo47fBBuBQztRawrOp", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:15:21.875Z", | |
"updatedAt": "2023-07-13T20:15:21.875Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo porting (desktop) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/c17bo47fBBuBQztRawrOp/e16450ef2ef20d0c7bf2dc709912fc1b/Frame_427319402__1_.png", | |
"details": { | |
"size": 4963066, | |
"image": { "width": 3840, "height": 2818 } | |
}, | |
"fileName": "Frame 427319402 (1).png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"dottedBackground": true, | |
"imagePercentageWidth": 100, | |
"fullWidth": false | |
} | |
} | |
}, | |
"content": [], | |
"nodeType": "embedded-entry-block" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "In our case, this means we need to have our Go code and Rust code interoperating with each other. We want to do a simple translation, explicitly avoiding making improvements or changing functionality when we're swapping out languages for the slice of code. That way, we can do intensive testing against both sets of code, and complete the migration as quickly as possible.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Why we didn't do a full rewrite", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Full rewrites are very tempting. They are more simple to write and ship, as you don't need to worry about your \"before\" and \"after\" code working together. You also get a clean slate to write a new and improved version, without the warts and technical debt of the previous iteration. However, full rewrites also come with some serious downsides.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": { | |
"target": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "3TdybzFpWSpOnKRG3VTkR2", | |
"type": "Entry", | |
"createdAt": "2023-07-13T20:19:57.965Z", | |
"updatedAt": "2023-07-13T20:19:57.965Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"contentType": { | |
"sys": { | |
"type": "Link", | |
"linkType": "ContentType", | |
"id": "mobileAndModeDependentMedia" | |
} | |
}, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo theoretical full rewrite", | |
"lightMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "365RREyHsEytwZlyGKa985", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:17:13.156Z", | |
"updatedAt": "2023-07-13T20:18:13.419Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo theoretical full rewrite (mobile) (light)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/365RREyHsEytwZlyGKa985/24ca1793a87a50ac37bdfea84c2964a5/iPhone_8_Plus_-_11.png", | |
"details": { | |
"size": 110687, | |
"image": { "width": 828, "height": 918 } | |
}, | |
"fileName": "iPhone 8 Plus - 11.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "WdkdfyBr5TwUufLIYBxRf", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:18:36.668Z", | |
"updatedAt": "2023-07-13T22:31:00.299Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo theoretical full rewrite (mobile) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/WdkdfyBr5TwUufLIYBxRf/f5066270eea29c7ef149ba6eb3c95528/iPhone_8_Plus_-_16.png", | |
"details": { | |
"size": 260914, | |
"image": { "width": 828, "height": 918 } | |
}, | |
"fileName": "iPhone 8 Plus - 16.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"lightDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "7nb5vSuSQZXEKsUUsYGNVl", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:19:16.711Z", | |
"updatedAt": "2023-07-13T20:19:16.711Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo theoretical full rewrite (desktop) (light)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/7nb5vSuSQZXEKsUUsYGNVl/f0f6be9ab08484fb087d5583d5720278/Frame_427319410.png", | |
"details": { | |
"size": 411233, | |
"image": { "width": 3840, "height": 1470 } | |
}, | |
"fileName": "Frame 427319410.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "7bF6sXF2wD092FiiuBXyGV", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:19:52.315Z", | |
"updatedAt": "2023-07-13T20:19:52.315Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo theoretical full rewrite (desktop) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/7bF6sXF2wD092FiiuBXyGV/f5f57209b1f25030cb3e106a2a6189d1/Frame_427319404__1_.png", | |
"details": { | |
"size": 3114415, | |
"image": { "width": 3840, "height": 1470 } | |
}, | |
"fileName": "Frame 427319404 (1).png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"dottedBackground": true, | |
"imagePercentageWidth": 100, | |
"fullWidth": false | |
} | |
} | |
}, | |
"content": [], | |
"nodeType": "embedded-entry-block" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "First, a full rewrite tends to require a complete halt to shipping new features. Otherwise, you run the risk of chasing a moving target as the old codebase grows while you try to catch up with your new code. ", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "A full rewrite also does not guarantee a better user experience. Often, a rewrite ends up less than seamless, as it's not feasible for the new version to match the old one, feature for feature, edge case for edge case. As the surface area of the rewrite grows, there's more room for error and users can end up frustrated with breaking changes and missing features.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Full rewrites also require building up an entirely new codebase, which is a large quantity of unused code. In our experience, unused code, even when verified with tests, can be a breeding ground for bugs. We wanted to make sure that any new Rust code was properly exercised as we moved through our porting effort.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "We chose to port", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Therefore, we decided to ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "bold" }], | |
"value": "port", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " Turborepo to Rust instead of doing a full rewrite.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Porting did necessitate some tradeoffs. We had to introduce a significant amount of complexity into our codebase, so that we could interoperate between Go and Rust. This complexity meant slower developer velocity to start, but we look forward to workflow improvements going forward, particularly when our porting effort has finished.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "More importantly, we knew we could continue shipping features to Turborepo users while porting. All things considered, we determined that this was a reasonable compromise and the path that we would take.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Starting the port ", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "We chose to start by writing a small, new Turborepo feature in Rust. This way, we could add new functionality from the roadmap for users, integrate Rust into our build process, and interact with existing Go code as little as possible to reduce our initial complexity.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Once we'd laid this groundwork, we knew that we could slowly port more and more code to Rust over time.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Global ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "We decided to have our first Rust feature be ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://turbo.build/blog/turbo-1-7-0#global-turbo?utm_source=turbo\u0026utm_medium=blog\u0026utm_campaign=turborepo_porting" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "global ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", a feature that allows users to install Turborepo as a globally available command on their machine. ", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "A global installation of ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " will look for a locally installed ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " program in the repository, execute it if it exists, and otherwise fallback to the global ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " binary. That way, you can easily run ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " from anywhere in your repository, but also keep a specific version of ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " pinned in your ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "package.json", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ".", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": { | |
"target": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "4UMkkcOozOR96U14rKUU6H", | |
"type": "Entry", | |
"createdAt": "2023-07-13T20:26:47.199Z", | |
"updatedAt": "2023-07-13T20:26:55.156Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"contentType": { | |
"sys": { | |
"type": "Link", | |
"linkType": "ContentType", | |
"id": "mobileAndModeDependentMedia" | |
} | |
}, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Global `turbo` diagram", | |
"lightMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "3DzjEq5stkry2IiCm6zh4U", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:24:51.983Z", | |
"updatedAt": "2023-07-13T20:24:51.983Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Global `turbo` diagram (mobile) (light)", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/3DzjEq5stkry2IiCm6zh4U/d5f38eda9664709a42fbbc2e8707ec74/iPhone_8_Plus_-_19.png", | |
"details": { | |
"size": 138803, | |
"image": { "width": 828, "height": 1294 } | |
}, | |
"fileName": "iPhone 8 Plus - 19.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "7beQhPRdj8OYxzfUcYgEbU", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:25:34.814Z", | |
"updatedAt": "2023-07-13T20:25:34.814Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Global `turbo` diagram (mobile) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/7beQhPRdj8OYxzfUcYgEbU/d2e5d1a6a0d94b65231122a21672217e/iPhone_8_Plus_-_5.png", | |
"details": { | |
"size": 626696, | |
"image": { "width": 828, "height": 1294 } | |
}, | |
"fileName": "iPhone 8 Plus - 5.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"lightDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "qPT2LsrOLLG4ISEyuv92G", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:26:15.171Z", | |
"updatedAt": "2023-07-13T22:24:32.703Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Global `turbo` diagram (desktop) (light)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/qPT2LsrOLLG4ISEyuv92G/2427d2381392e4acc95dc51d1cf9edca/Frame_427319413.png", | |
"details": { | |
"size": 560468, | |
"image": { "width": 3300, "height": 1480 } | |
}, | |
"fileName": "Frame 427319413.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "2gbJI96SblT7dXxZWZDYsD", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:26:39.581Z", | |
"updatedAt": "2023-07-13T22:24:46.147Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Global `turbo` diagram (mobile) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/2gbJI96SblT7dXxZWZDYsD/6877030625b5c53c3523ad96db1d468d/Frame_427319414.png", | |
"details": { | |
"size": 1325355, | |
"image": { "width": 3300, "height": 1480 } | |
}, | |
"fileName": "Frame 427319414.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"dottedBackground": true, | |
"imagePercentageWidth": 100, | |
"fullWidth": false | |
} | |
} | |
}, | |
"content": [], | |
"nodeType": "embedded-entry-block" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "This feature is implemented through what we started calling \"the Rust shim,\" a bit of Rust code that wraps the existing Go code. The Go portion is compiled via CGO as a C static library and then linked to the Rust binary. Luckily, global ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " only required a few features from the rest of Turborepo's code, such as reading configuration and navigating the file system. ", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "CLI parsing", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "As we implemented global ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", we realized we needed to parse a few command line arguments like ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "--cwd", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", the argument for setting ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "'s current working directory.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "After global ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", it made sense to continue by porting the rest of the CLI argument parser to Rust. To parse arguments, we use the ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { "uri": "https://docs.rs/clap/latest/clap/" }, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "clap", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " crate", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " (Rust’s equivalent of an npm package). ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "clap", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " lets you define a data type with the arguments, annotate it a little bit, and it will automatically create a parser.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "With the pieces in place, we had to work on sending the args from the Rust entry point to the Go code. For better or worse, ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://faultlore.com/blah/c-isnt-a-language/" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "C is the standard for foreign function interfacing (FFI)", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", so we had to use C to communicate between Rust and Go.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": { | |
"target": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "1bBJLXpcb54CuGB64UsuiB", | |
"type": "Entry", | |
"createdAt": "2023-07-21T08:32:48.535Z", | |
"updatedAt": "2023-07-21T08:32:48.535Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"contentType": { | |
"sys": { | |
"type": "Link", | |
"linkType": "ContentType", | |
"id": "mobileAndModeDependentMedia" | |
} | |
}, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "FFI Diagram", | |
"lightMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "1M9EpwZDGRt4PqXnS41B6p", | |
"type": "Asset", | |
"createdAt": "2023-07-21T08:28:27.587Z", | |
"updatedAt": "2023-07-21T08:36:17.706Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "FFI Diagram - Light Mobile", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/1M9EpwZDGRt4PqXnS41B6p/0cc3e7336ee39882508a6dd112db9a5f/InlineGraphic_1920xVariable__3_.png", | |
"details": { | |
"size": 106784, | |
"image": { "width": 828, "height": 832 } | |
}, | |
"fileName": "InlineGraphic_1920xVariable (3).png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "7s2Gq0JKjOQkBQ9ZAOqEGw", | |
"type": "Asset", | |
"createdAt": "2023-07-21T08:30:59.891Z", | |
"updatedAt": "2023-07-21T08:36:25.836Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "FFI Diagram - Dark Mobile", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/7s2Gq0JKjOQkBQ9ZAOqEGw/4b0db3626aa7e7b7b10b95e8fdf8d631/InlineGraphic_1920xVariable__2_.png", | |
"details": { | |
"size": 254655, | |
"image": { "width": 828, "height": 832 } | |
}, | |
"fileName": "InlineGraphic_1920xVariable (2).png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"lightDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "2FGPm6ksIA7bGzZzY7CYkd", | |
"type": "Asset", | |
"createdAt": "2023-07-21T08:30:46.358Z", | |
"updatedAt": "2023-07-21T08:36:34.452Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "FFI Diagram - Light Desktop", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/2FGPm6ksIA7bGzZzY7CYkd/06b7d487cbcf5237ae9f917574cf7a51/InlineGraphic_1920xVariable__1_.png", | |
"details": { | |
"size": 431419, | |
"image": { "width": 3840, "height": 2168 } | |
}, | |
"fileName": "InlineGraphic_1920xVariable (1).png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "7sVbowOCnYSjNrOyxfD5NF", | |
"type": "Asset", | |
"createdAt": "2023-07-21T08:32:39.284Z", | |
"updatedAt": "2023-07-21T08:36:42.331Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "FFI Diagram - Dark", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/7sVbowOCnYSjNrOyxfD5NF/0fdf484cf4ced36f67e0ab879514d099/InlineGraphic_1920xVariable.png", | |
"details": { | |
"size": 3230285, | |
"image": { "width": 3840, "height": 2168 } | |
}, | |
"fileName": "InlineGraphic_1920xVariable.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"dottedBackground": true, | |
"imagePercentageWidth": 100, | |
"fullWidth": false | |
} | |
} | |
}, | |
"content": [], | |
"nodeType": "embedded-entry-block" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "We wanted to avoid having too many types in C, as we weren’t confident that we could write cross-platform C types that played well with both Rust and Go. Instead, we decided to serialize our arguments to JSON and send it to Go as a string. Even though JSON serialization does have some overhead, we knew that the arguments struct would only be a few hundred bytes in size, so the performance impact would be minimal.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "On the Rust side, we used another cornerstone crate of the Rust ecosystem, ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://docs.rs/serde/latest/serde/" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "serde", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", which lets you serialize and deserialize data in various different formats, using some minimal annotation. For the Go side, we were already using JSON in the codebase, so it was easy to receive the JSON string and deserialize it into a Go struct. ", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Ship it?", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "With these two features ported, we were ready to ship our first hybrid Go-Rust release.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "However, before we could release, we needed to make sure the Go-Rust binary worked in all the various contexts that Turborepo is used, ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://turbo.build/repo/docs/installing?utm_source=turbo\u0026utm_medium=blog\u0026utm_campaign=turborepo_porting" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "like the different operating systems and Linux distros that we support", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ". As we tested our code, we started noticing some issues on a couple platforms.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Windows difficulties", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "On Windows, there are two main toolchains, ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Microsoft Visual C++ (MSVC)", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " and ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://en.wikipedia.org/wiki/MinGW" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Minimalist GNU for Windows (MinGW)", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ".", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Go ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "bold" }], | |
"value": "only", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " uses MinGW, but we were using Rust with MSVC. This caused some runtime issues, but, luckily, the solution was simple: we moved our Rust toolchain to MinGW.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Next up, we had some issues with paths. Windows has a couple concepts of paths, including what’s called a Universal Naming Convention (UNC) path. When you ask Windows to canonicalize a path (resolve all symlinks and normalize components of the path), it gives you a UNC path.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "However, despite the name, UNC paths are not supported everywhere—sometimes not even by Windows itself! This caused a few bugs where we’d provide a UNC path and get an invalid path error. The solution was to use a helpful Rust crate called ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://docs.rs/dunce/latest/dunce/" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "dunce", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " that lets you canonicalize a path and get a non-UNC path back, handling the intricacies of this problem for us.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Alpine Linux", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "The second set of challenges came with Alpine Linux. At Vercel, we use Alpine, a common operating system for cloud computing, to create lightweight containers for building your projects.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Alpine, though, does not come with ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "glibc", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", the de-facto implementation of the C standard library. This is a problem because many binaries assume ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "glibc", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " is installed and don’t package it themselves. There are some libraries that pave over this issue using packages like ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "gcompat", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " or ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "libc6-compat", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", but they didn’t end up working for us because the version of ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "glibc", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " that Rust requires was too modern for our supported targets. When we’d try to run the binary, we’d get errors that the required ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "glibc", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " version was not available.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "As a result, we decided to compile Turborepo as a fully static binary. This meant that we packaged our own C standard library implementation using ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "musl", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " (since you can't statically link ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "glibc", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " due to licensing issues). This seems to work just fine for both Rust and Go: Rust lets you set the C standard library in the target (", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "aarch64-unknown-linux-musl", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " vs. ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "aarch64-unknown-linux-gnu", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ") and Go does not use a C standard library by default.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "However, when we ran this statically linked binary, it would return a segmentation fault. Even worse, when we inspected with a debugger, we’d find a corrupted stack. And, even worser, the segfault appeared to be coming from the Go runtime itself!", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ "data": {}, "content": [], "nodeType": "hr" }, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "After a lot of searching, we tracked down a ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://github.com/golang/go/issues/13492" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "seven year-old GitHub issue", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " which explained that Go cannot be compiled as a C static library with ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "musl", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ". This posed a significant challenge, as Alpine Linux is an essential platform for Turborepo and its users. We had to go back to the drawing board and figure out how we could ship our Go-Rust combination.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Eventually, after a ton more deliberation, we came up with a solution: we’d compile our Go code and our Rust code as two separate binaries. The Rust code would call the Go code and pass the args serialized to JSON via the CLI.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": { | |
"target": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "5480fFG0GkH8KJ2Du1IOmQ", | |
"type": "Entry", | |
"createdAt": "2023-07-13T20:23:38.663Z", | |
"updatedAt": "2023-07-13T20:23:38.663Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"contentType": { | |
"sys": { | |
"type": "Link", | |
"linkType": "ContentType", | |
"id": "mobileAndModeDependentMedia" | |
} | |
}, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo data pipeline", | |
"lightMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "5QibrcV35p6zu0Rpzriwn5", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:21:42.534Z", | |
"updatedAt": "2023-07-13T20:21:42.534Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo data pipeline (mobile) (light)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/5QibrcV35p6zu0Rpzriwn5/57872cf4a380ba9c3ec780a02c370775/iPhone_8_Plus_-_14.png", | |
"details": { | |
"size": 88421, | |
"image": { "width": 828, "height": 1012 } | |
}, | |
"fileName": "iPhone 8 Plus - 14.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkMobileVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "2547NHodIcPCgR7ITmjnM0", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:22:11.314Z", | |
"updatedAt": "2023-07-13T22:36:50.113Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 2, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo data pipeline (mobile) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/2547NHodIcPCgR7ITmjnM0/4366bba1f016889729cfc1c729554907/iPhone_8_Plus_-_18.png", | |
"details": { | |
"size": 257312, | |
"image": { "width": 828, "height": 1012 } | |
}, | |
"fileName": "iPhone 8 Plus - 18.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"lightDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "1VOl0eTFS33pkNLbtrzhY", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:23:20.327Z", | |
"updatedAt": "2023-07-13T20:23:20.327Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo data pipeline (desktop) (light)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/1VOl0eTFS33pkNLbtrzhY/6811cbf8065443e0ba46c5c365b9fca4/Frame_427319412.png", | |
"details": { | |
"size": 302497, | |
"image": { "width": 3840, "height": 1834 } | |
}, | |
"fileName": "Frame 427319412.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"darkDesktopVersion": { | |
"metadata": { "tags": [] }, | |
"sys": { | |
"space": { | |
"sys": { | |
"type": "Link", | |
"linkType": "Space", | |
"id": "e5382hct74si" | |
} | |
}, | |
"id": "3KbndugMu2gM4Nu8Eoq3dj", | |
"type": "Asset", | |
"createdAt": "2023-07-13T20:22:52.904Z", | |
"updatedAt": "2023-07-13T20:22:52.904Z", | |
"environment": { | |
"sys": { | |
"id": "master", | |
"type": "Link", | |
"linkType": "Environment" | |
} | |
}, | |
"revision": 1, | |
"locale": "en-US" | |
}, | |
"fields": { | |
"title": "Turborepo data pipeline (desktop) (dark)", | |
"description": "", | |
"file": { | |
"url": "//images.ctfassets.net/e5382hct74si/3KbndugMu2gM4Nu8Eoq3dj/41cb0b6578dbd822c77f08f2bada4066/Frame_427319408.png", | |
"details": { | |
"size": 2882497, | |
"image": { "width": 3840, "height": 1834 } | |
}, | |
"fileName": "Frame 427319408.png", | |
"contentType": "image/png" | |
} | |
} | |
}, | |
"dottedBackground": true, | |
"imagePercentageWidth": 100, | |
"fullWidth": false | |
} | |
} | |
}, | |
"content": [], | |
"nodeType": "embedded-entry-block" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "We knew that the args were small enough that they could be passed via CLI without too much of a performance hit. And because we were using a serialization format, the code changes were extremely small. All we had to do was change how Rust was sending the JSON string to Go.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "With that, we were able to get our first hybrid Go-Rust release out the door. The first version of ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "code" }], | |
"value": "turbo", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " that was shipped to you using these compilation strategies was ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://turbo.build/blog/turbo-1-7-0?utm_source=turbo\u0026utm_medium=blog\u0026utm_campaign=turborepo_porting" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "version 1.7.0", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ".", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "What we learned", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Through this effort, we've learned a lot about moving from one language to another. Let's take note of what we've found.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Serialization is useful for FFI", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Our first takeaway is that serialization formats are very useful for interoperability. By serializing to JSON, a format with robust support in both Go and Rust, we were able to minimize our FFI surface area, and avoid a whole class of cross-platform, cross-language bugs. When we had to switch from a single, linked binary to two binaries, we were able to do so with relative ease because our FFI surface area was so small.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "The tradeoff here is that serialization and deserialization is slow. You can only depend on this technique if either you know your serialized payloads will be small or you don't care about the performance hit for your use case.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Porting takes preparation", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "The second takeaway is that incremental porting is feasible but requires lots of careful testing and strategizing. We ran into quite a few tricky bugs and we caught these issues through lots of automated and manual testing. You can ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://github.com/vercel/turbo/tree/main/.github/workflows" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "check out our (and Turbopack's) testing suites in our GitHub workflows", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ".", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Testing is also extremely important for nailing down the behavior of your code, whether it’s the exact edge cases of CLI parsing, or the order in which configuration is loaded. These exact details are not so crucial when you’re writing your first implementation, but they’re absolutely paramount to avoid breaking changes during a port or rewrite. You should aim to write tests ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "bold" }], | |
"value": "before", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " you start porting code, so that you have a known specification to work against.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Cross-compatibility is difficult", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "The third takeaway is that cross-platform, cross-language release engineering is extremely challenging. Every platform, language, and compiler has their own quirks that can make interoperability difficult and, the more things you have working together, the more opportunities you have for a new complication.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Porting is worth it for us", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-3" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Finally, while porting from Go to Rust has been challenging, it has proven to be the correct choice for us strategically. Even with our porting effort going on, we've been able to ship new features, handle bugs in existing functionality, and keep helping our users while we migrate. It's required some extraordinarily tricky debugging, careful planning, and rigorous testing, but we believe it has been worth it.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Try out (ported) Turborepo", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "heading-2" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "This week, Turborepo saved 5,742 hours of time for the product engineers and CI machines at Vercel. If you want to try out the same technology in just a few minutes, ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://vercel.com/blog/vercel-remote-cache-turbo" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "check out our article", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " on how you can get started with ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://vercel.com/docs/concepts/monorepos/remote-caching" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Vercel Remote Cache", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ".", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
} | |
], | |
"nodeType": "document" | |
}, | |
"category": { | |
"name": "Engineering", | |
"slug": "engineering", | |
"description": "See how we’re building the future for frontend developers." | |
}, | |
"date": "2023-07-21T13:00:00.000Z", | |
"description": "Our strategy for making updates and maintaining stability while we migrate languages.", | |
"id": "how-turborepo-is-porting-from-go-to-rust", | |
"isChangelog": false, | |
"isDraft": false, | |
"fullWidthCoverImage": { | |
"dark": { | |
"title": "", | |
"height": 1323, | |
"width": 2529, | |
"source": "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/7Ig7FQeMzQ5f0E5f78Qjjp/55ab8aec8f2dd11481288e624108785f/OG_Image_____Template.png" | |
}, | |
"light": { | |
"title": "", | |
"height": 1323, | |
"width": 2529, | |
"source": "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/41iFVCHuBs1rQn4jTfRGIt/3bd01ff806eb9ab64b31445dad31cd6c/OG_Image_____Template__1_.png" | |
} | |
}, | |
"productsInvolved": [], | |
"kpi1ImprovementTitle": null, | |
"kpi1ImprovementDescription": null, | |
"kpi2ImprovementTitle": null, | |
"kpi2ImprovementDescription": null, | |
"isLegacyChangelog": false, | |
"link": "/blog/how-turborepo-is-porting-from-go-to-rust", | |
"noIndex": null, | |
"preview": { | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "In ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://vercel.com/blog/turborepo-migration-go-rust" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "a previous blog post", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", we talked about ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "bold" }], | |
"value": "why", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": " we are porting ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": { | |
"uri": "https://turbo.build?utm_source=turbo\u0026utm_medium=blog\u0026utm_campaign=turborepo_porting" | |
}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Turborepo, the high-performance build system for JavaScript and TypeScript", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "hyperlink" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ", from Go to Rust. Now, let's talk about ", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [{ "type": "bold" }], | |
"value": "how", | |
"nodeType": "text" | |
}, | |
{ | |
"data": {}, | |
"marks": [], | |
"value": ".", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Today, our porting effort is in full swing, moving more and more code to Rust. But when we were starting out, we had to make sure that porting was feasible for us to accomplish. A migration from one language to another is no small task and there's a lot of research to do up front to ensure that the end goal is attainable. ", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
}, | |
{ | |
"data": {}, | |
"content": [ | |
{ | |
"data": {}, | |
"marks": [], | |
"value": "Here’s how we started the process, validated our current porting strategy, and made the call to port Turborepo to Rust.", | |
"nodeType": "text" | |
} | |
], | |
"nodeType": "paragraph" | |
} | |
], | |
"nodeType": "document" | |
}, | |
"recommended": null, | |
"slug": "how-turborepo-is-porting-from-go-to-rust", | |
"title": "How Turborepo is porting from Go to Rust", | |
"seoTitle": "How Turborepo is porting from Go to Rust", | |
"subtitle": "Our strategy for making updates and maintaining stability while we migrate languages.", | |
"url": "https://vercel.com/blog/how-turborepo-is-porting-from-go-to-rust", | |
"heroImageDark": { | |
"width": 1200, | |
"height": 627, | |
"source": "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6E56Ftg7W5wsRF8rjgjS7N/42f14e2661f473af626111a517df547e/Turbo_OG.png" | |
}, | |
"ogImage": { | |
"width": 1200, | |
"height": 627, | |
"source": "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6E56Ftg7W5wsRF8rjgjS7N/42f14e2661f473af626111a517df547e/Turbo_OG.png" | |
}, | |
"authors": [ | |
{ | |
"bio": null, | |
"companyName": null, | |
"companyWebsite": null, | |
"real": "Nicholas Yang", | |
"role": "Turborepo Core Team", | |
"twitter": "NicholasLYang", | |
"vercel": "nicholaslyang", | |
"vercelian": true | |
}, | |
{ | |
"bio": "Anthony Shew is a Content Engineer at Vercel focused on creating content to help Next.js and Vercel users. Previously, he co-founded a digital agency creating software in the sports industry leveraging his experience playing professional baseball.", | |
"companyName": "Vercel", | |
"companyWebsite": null, | |
"real": "Anthony Shew", | |
"role": "Content Engineer", | |
"twitter": "anthonysheww", | |
"vercel": "anthony-shew", | |
"vercelian": true | |
} | |
], | |
"communityAuthors": [], | |
"relatedPosts": [ | |
{ | |
"id": "turborepo-migration-go-rust", | |
"title": "Why Turborepo is migrating from Go to Rust", | |
"slug": "turborepo-migration-go-rust", | |
"authors": [ | |
{ | |
"bio": "Anthony Shew is a Content Engineer at Vercel focused on creating content to help Next.js and Vercel users. Previously, he co-founded a digital agency creating software in the sports industry leveraging his experience playing professional baseball.", | |
"companyName": "Vercel", | |
"companyWebsite": null, | |
"real": "Anthony Shew", | |
"role": "Content Engineer", | |
"twitter": "anthonysheww", | |
"vercel": "anthony-shew", | |
"vercelian": true | |
}, | |
{ | |
"bio": "Jared Palmer is the creator and founder of Turborepo (acquired by Vercel). He’s also created many popular open-source projects such as Formik and TSDX and is a native New Yorker.", | |
"companyName": "Vercel", | |
"companyWebsite": "Turborepo.org", | |
"real": "Jared Palmer", | |
"role": "VP of Product, AI \u0026 Strategy", | |
"twitter": "jaredpalmer", | |
"vercel": "jared", | |
"vercelian": true | |
}, | |
{ | |
"bio": null, | |
"companyName": "Vercel", | |
"companyWebsite": "https://vercel.com", | |
"real": "Greg Soltis", | |
"role": "Turborepo Core Team", | |
"twitter": "gsoltis", | |
"vercel": "gsoltis", | |
"vercelian": true | |
}, | |
{ | |
"bio": null, | |
"companyName": null, | |
"companyWebsite": null, | |
"real": "Nathan Hammond", | |
"role": "Turborepo Core Team", | |
"twitter": "nathanhammond", | |
"vercel": "nathanhammond", | |
"vercelian": true | |
}, | |
{ | |
"bio": null, | |
"companyName": null, | |
"companyWebsite": null, | |
"real": "Nicholas Yang", | |
"role": "Turborepo Core Team", | |
"twitter": "NicholasLYang", | |
"vercel": "nicholaslyang", | |
"vercelian": true | |
} | |
] | |
}, | |
{ | |
"id": "vercel-remote-cache-turbo", | |
"title": "Faster iteration with Turborepo and Vercel Remote Cache", | |
"slug": "vercel-remote-cache-turbo", | |
"authors": [ | |
{ | |
"bio": "Anthony Shew is a Content Engineer at Vercel focused on creating content to help Next.js and Vercel users. Previously, he co-founded a digital agency creating software in the sports industry leveraging his experience playing professional baseball.", | |
"companyName": "Vercel", | |
"companyWebsite": null, | |
"real": "Anthony Shew", | |
"role": "Content Engineer", | |
"twitter": "anthonysheww", | |
"vercel": "anthony-shew", | |
"vercelian": true | |
} | |
] | |
} | |
], | |
"exploreCards": [ | |
{ | |
"id": "turborepo-next-basic", | |
"type": "template", | |
"title": "Turborepo \u0026 Next.js Starter", | |
"slug": "turborepo-next-basic", | |
"framework": "Next.js", | |
"publisher": "▲ Vercel", | |
"thumbnailImage": "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/2JxNyYATuuV7WPuJ31kF9Q/433990aa4c8e7524a9095682fb08f0b1/Basic.png", | |
"thumbnailImageWidth": 1200, | |
"thumbnailImageHeight": 800 | |
}, | |
{ | |
"id": "https://turbo.build", | |
"type": "landing-page", | |
"title": "Visit turbo.build", | |
"url": "https://turbo.build", | |
"image": "https://assets.vercel.com/image/upload/contentful/image/e5382hct74si/6AXDBERJ8a3qkQkJbDMojJ/47691b985c3f224b0e2742564a3a5ef1/CleanShot_2023-07-13_at_14.50.43_2x.png" | |
} | |
] | |
}, | |
"tweets": [] | |
}, | |
"__N_SSG": true | |
}, | |
"page": "/blog/[slug]", | |
"query": { "slug": "how-turborepo-is-porting-from-go-to-rust" }, | |
"buildId": "iFgjysQlqiq_betPy2rns", | |
"isFallback": false, | |
"gsp": true, | |
"scriptLoader": [] | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment