多年来,网络社区赞同单个分页申请(SPA)架构的益处。几乎任何Web会议或流行的技术博客,你一定会遇到一个关于他们的福利的讨论。它使我们为什么这是一个感觉:每次导航到网站的不同页面时,等待加载和初始化HTML,JavaScript和样式表的完整页面;按需加载小型JavaScript是快速的。SPA也允许我们避免在页面加载之间避免可怕的空白屏幕,而让我们提供更多用户友好的加载屏幕。这使得SPAs比传统架构更适合用于在某些国家/地区的移动网络,甚至是桌面网站的连接环境。如果正确优化,即使是第一页的负载也可以相当瘦;我甚至不会进入着名的财务激励,提供快速浏览体验。简短版本是:得快

但是,许多SPA讨论不考虑哪些水疗讨论是如何迁移到单页架构的过程。

尤其是当您处理的是一个成熟的应用程序时,它有数百万的用户和成堆的不易理解的代码。这是可以理解的,为什么不谈论太多,许多最有发言权的科技公司往往分为两类之一:要么他们是一个相对较新的初创企业,科技债务按比例很少;要么他们是一个更成熟的FAANG类型,有几乎无限的资源来解决科技债务。

在Ok188bet金宝搏官网Cupid,我们从2004年就开始工作了,有一个相当小的(约30人)工程团队全程为事情提供动力。我们的web代码库早于rubyonrails,当您得知我们的技术栈依赖于一个类似PHP的服务器端呈现框架(我们称之为Pub)时,您可能会感到惊讶(或者相当震惊)。直到最近,我们的产品还是Pub、vanilla JS、jQuery、Coffescript、React、Reflux、Redux等的拼凑,主要依赖服务器端的水合状态来代替适当的api。此外,还有无数可能死掉的代码路径和有条件地注入的有用性值得怀疑的第三方脚本。所以,当我们谈论遗留代码和技术债务时,这就是我们要讨论的问题。

And as many of you know, it’s often difficult to make a business justification for a ground-up rewrite of a product. So how do you take a web application older than some JavaScript programmers and bring it into the modern era? Well, we’ve actually been making significant progress towards this goal for a while now. The fact that React can easily render into an existing app was crucial to helping us start adopting it, as early as mid-2016—and today our product is thankfully almost entirely React-powered. I’m also happy to report that all of our product pages are API-driven (though we’re not quite at the GraphQL-powered utopia we’re striving towards—more on that later). Both of these were slow, gradual transitions that we've been working at for years, and that made our eventual move to a SPA significantly easier.

但是,尽管取得了这么大的进步,直到最近,我们仍然依赖Pub来实时生成每个请求的HTML页面。这使我们无法做一些很酷的事情,比如不让程序员学习一门编造的语言(对招聘非常有用)。所以大约6个月前,我开始一个项目,让我们离开酒吧。我们用我们的移动网络产品开始了这一转变,并在3个月后将这些经验带到了桌面网络。这两个平台的任务清单都很广泛,但相当简单:

  • 在编译时生成HTML文件以提供我们的JavaScript捆绑包。
  • Ensuring a fast (enough) first paint.
  • 使用代码拆分来避免最终得到一个臃肿的包。
  • 确定样品板逻辑等更好的抽象,如第三方脚本。
  • 更新遗留vanilla javascript工具依靠直接DOM操作进行反应。
  • 消除任何未使用的代码,我找到了一路。

在这种大规模迁移的过程中,我发出了一些观察结果,并设计了一种夫妻抽象,我希望您可以在清理旧代码以及编写新代码时发现有用。

追踪死亡代码

因此,让我们从最合乎逻辑的方式开始解决这些项目符号:按字母顺序排列。消除未使用的代码是迁移我们的传统基础架构时的巨大目标。部分这是理想主义的(Yay删除代码!),部分这是务实的(参与少代码转换!)。但是,令人难以置疑的挑战是在遗留的代码对保持我们的网站工作中至关重要的挑战,以及可以安全地删除哪些代码,因为从Okcupid托管期刊和论坛的日子里没有相关(是的,我们曾经做过那样188bet金宝搏官网;不,我不能告诉你为什么我们认为这是一个好主意)。然而,在我们的工具带中有一个非常基本的工具,证明了在弄清楚的代码是运行的并且不是:分析!

甜蜜,甜蜜的沉默。

有很多代码,我偶然发现了我的工程侵权队已经有了远见,以便将分析事件添加到。当我找到这些时,我会检查他们是否在去年左右被解雇,并在什么卷中被解雇,并判断判断是否可以切除违规代码。This honestly made me want to add an analytic event to every single React component I ever write. Someone talk me out of it.

如果只有一切都像发射分析事件一样简单!

优化第一个油漆

迁移到单个页面应用程序的另一个困难方面是我们的大部分网络应用程序依赖于我们的React应用程序中始终保证的某些数据。虽然这很易于确保使用服务器端数据保健程序,但在移动到静态HTML模板时,这更具挑战性。摆脱所有这些依赖性都会非常繁琐,难以致讨,所以我们必须制定解决问题的策略。最后,我们解决了这样的东西:

// index.js - our main application entry point import React from "react"; import ReactDOM from "react-dom"; import API from "./api"; import Helpers from "./helpers"; import App from "./app"; import AppError from "./app_error"; const root = document.getElementById("root"); // Load the absolute necessities. API.loadCriticalData() .then(() => Helpers.initializeGlobals()) .then(() => ReactDOM.render(, root)) .catch((error) => ReactDOM.render(, root));

它所做的是加载对我们的应用程序绝对重要的数据。然后,通过初始化一些全局库,使数据可供应用程序的其余部分使用。最后,它将应用程序呈现给DOM。如果此过程中的任何操作失败,我们将返回错误状态。

然而,这种方法有一个重要的缺点:在我们等待数据加载时呈现的是什么?我们不想向用户展示一个空白屏幕,而这发生 - 即使它只会每次会话发生一次,也显示用户一个空白屏幕永远不会理想。要打击此功能,我们将使用App Shell填充我们的模板文件:

// index.html   188bet金宝搏官网 okcupid </ title> </ head> <body> <div id =“root”> <div类=“some-loading-state”> <div类=“一些闪烁的装载机”> </ div> </ div> <noscript>打开javascript,混蛋!</ noscript> </ div> </ body> </ html></code></pre>
        <p>React will eventually render into the "root"<code>div</code>,并消除它内部的一切;直到它发生,我们可以在那里提供任何东西<code>div</code>我们希望,包括一个应用程序外壳,以显示用户在此期间或<code>题词</code>使用UI在没有JavaScript的情况下向用户展示。这可确保在JavaScript上加载线路,解析,编译,然后加载所需的数据时,用户不会被迫坐在空白屏幕前。<a href="https://medium.com/reloading/javascript-start-up-performance-69200f43b201">Learn more about JavaScript Startup Performance here.</a></p>
        <h3 id="code-splitting">代码拆分</h3>
        <figure class="kg-card kg-image-card">
         <img src="//www.bizviewz.com/content/images/2020/02/giphy--4-.gif" class="kg-image" alt>
        </figure>
        <p>Another critical aspect of ensuring a fast first paint is making sure our JavaScript bundle didn't balloon in size as our Single Page App added more and more routes. Fortunately these days, React offers some great out-of-the-box tools to prevent this from happening. Namely:<code>React.lazy</code>和<code>React.Suspense.</code>。</p>
        <p>如果你不熟悉<code>React.lazy</code>,这是一个伟大的补充,是最近发布的<code>React@16.0.</code>,它允许我们按需动态加载任何组件。您不再需要第三方库到基于当前路由的代码拆分;相反,您可以做到这样的事情:</p>
        <pre><code class="language-JSX">// Routes.jsx import React from "react"; import { Switch, Route } from "react-router-dom"; import Loading from "./Loading"; // Some loading state UI. const Home = React.lazy(() => import("pages/home")); const Login = React.lazy(() => import("pages/login")); const Signup = React.lazy(() => import("pages/signup")); const Routes = () => ( <React.Suspense fallback={<Loading />}> <Switch> <Route exact path="/" component={Home} /> <Route exact path="/login" component={Login} /> <Route exact path="/signup" component={Signup} /> </Switch> </React.Suspense> );</code></pre>
        <p>在这里,我正在使用反应路由器<code>Switch</code>和<code>路线</code>在给定路径处处理呈现组件的组件。它对它周围的懒惰加载逻辑完全不可知,您可以选择使用任何或任何没有库代替它。</p>
        <p>在这里发生了什么是<code>Switch</code>成分will decide which<code>路线</code>to render based off the current path. When it finds one that matches, the<code>路线</code>将呈现传递给其<code>成分</code>道具。路由器不知道的是,在这种情况下,这些组件中的每一个都是动态加载的<em>只有在他们将被渲染之后</em>。<code>React.Suspense.</code>will actually hold off on rendering until the component loads. While it's loading, it will render whatever you pass to its<code>倒退</code>同时的道具。这意味着向我们的水疗中心添加额外的路线几乎没有成本到主捆绑!</p>
        <p>将来,它很可能会允许我们使用<code>React.Suspense.</code>暂停呈现数据时获取数据(<code>中继</code>,React的GraphQL框架,已经实现了这个),或者发生任何其他异步操作。但它足以用于今天生产中的代码拆分是稳定的,就像我们一样!<a href="https://reactjs.org/docs/code-splitting.html">了解更多关于代码拆分的更多信息</a>。</p>
        <h3 id="third-party-scripts-with-usescript">具有符号的第三方脚本</h3>
        <figure class="kg-card kg-image-card kg-width-full kg-card-hascaption">
         <img src="//www.bizviewz.com/content/images/2020/02/Screen-Shot-2020-02-21-at-12.30.10-AM-1.png" class="kg-image" alt>
         <figcaption>
          <em>I won't pretend I know what Google Analytics is doing here. But, really?</em>
         </figcaption>
        </figure>
        <p>我发现自己在SPA迁移期间做的最常见的事情之一就在一些第三方剧本中粘贴了几乎所有这些都看起来像这样的东西:</p>
        <pre><code class="language-HTML/JavaScript"><! -  Google Analytics  - > <脚本>(功能(i,s,o,g,r,a,m){i ['googleanalyticsobject'] = r; i [r] = i [r] ||function(){(i [r] .q = i [r] .q || [])。按(参数)},i [r] .l = 1 *新日期(); a = s.createelement(o),m = s.getElementsByTagname(O)[0]; a.async = 1; a.src = g; m.parentnode.insertBefore(a,m)})(窗口,文档,'脚本','https://www.bizviewz.com/analytics.js'ar'ga');ga('create','ua-xxxx-y','auto');GA('发送','pageview');</ script> <! - 结束Google Analytics  - ></code></pre>
        <p>这些通常如上预先缩小,但是要点对每个单一的GIST都是:向身体添加一个脚本标记,从CDN加载一些第三方JavaScript,并运行一些相关的代码。</p>
        <p>But some of these scripts we really only wanted to load in some cases (e.g. for only logged in users, only for users with ads, only for users with higher data connectivity, etc.), and there's not much room for this kind of nuance within a static HTML template. Fortunately, React Hooks ended up being a great tool to handle this! We ended up with something that looked like this:</p>
        <pre><code class="language-JSX">// usercript.js导入{useref,defereffect}从“反应”;/ ** *动态加载JavaScript。* @param {string} URL  - 要加载脚本的URL。* @param {object} [选项= {}]  - 脚本元素的任何覆盖。* / function usersctift(URL,选项= {}){//创建脚本元素一次。const scriptref = useref(document.createelement(“脚本”));使用(()=> {//基本设置。contrincrect = scriptref.crent; script.type =“text / javascript”; script.src = url; //高级修改,和/或订阅事件。object.keys(选项).foreach((key)=>脚本[key] = props [key]);如果需要,添加到正文。如果(!document.body.contains(脚本)){document.body.appendchild(脚本);}},[URL,选项]);导出默认使用符号;</code></pre>
        <p>因为这些脚本通常需要更多的设置,所以我通常的方法是这样包装它们:</p>
        <pre><code class="language-JSX">/ / useGoogleAnalytics.js进口{useState useEffect, useRef } from “react”; import useScript from “./useScript”; const GOOGLE_ANALYTICS_SDK_URL = “//www.bizviewz.com/analytics.js”; /** * Load and use the Google Analytics SDK. * @param {object} location - the current user location. */ function useGoogleAnalytics(location = window.location) { // Track load state. const [hasLoaded, setHasLoaded] = useState(false); // Load the SDK. const sdkOptions = useRef({ onload: () => setHasLoaded(true) }); useScript(GOOGLE_ANALYTICS_SDK_URL, sdkOptions.current); // Initialize Google Analytics. useEffect(() => { // Exit if the SDK hasn’t loaded yet. if (!hasLoaded) { return; } window.ga('create', 'UA-XXXXX-Y', 'auto'); }, [hasLoaded]); // Track page views. const path = location.pathname; useEffect(() => { // Exit if the SDK hasn’t loaded yet. if (!hasLoaded) { return; } window.ga.current(“send”, “pageview”); }, [path, hasLoaded]); } export default useGoogleAnalytics;</code></pre>
        <p>Such that when we needed to use them, it was as simple as this:</p>
        <pre><code>// 页面.jsx从“react router dom”导入{useLocation};从“/useGoogleAnalytics”导入useGoogleAnalytics;函数BasicPage(){const location=useLocation();useGoogleAnalytics(location);返回(<div><header/><main/><footer/></div>);}</code></pre>
        <p>注意:我通过<code>地点</code>as a parameter here, instead of placing the<code>USELOCATION.</code>在钩子里呼叫。令人沮丧的是,我很早就发现React路由器的钩子会抛出运行时错误,如果您尝试在<code>路线r</code>上下文,这对于与非SPA页面的互操作性来说很麻烦。路过<code>地点</code>作为参数而不是调用<code>USELOCATION.</code>直接在钩子中,我们能够更容易地跨SPA和非SPA代码重用它。</p>
        <p>加载脚本的方法符合任何数量的辅助脚本,同时仍然易于阅读开发人员扫描代码!了解有关React Hooks的更多信息,<a href="//www.bizviewz.com/getting-hooked-on-react-hooks/">请看我之前关于这个问题的博文</a>。</p>
        <h3 id="migrating-vanilla-javascript-tools">迁移JavaScript工具</h3>
        <p>我们遇到的另一个问题是如何迁移我们的一些遗留UI工具,这些工具依赖于vanilla JavaScript和手动DOM操纵的组合。通常,我们有一些看起来像这样的东西:</p>
        <pre><code class="language-JavaScript">//遗产_流行音乐.jsconst LegacyPopover={init({theme}){const dom=文档.getElementById(“popover dom”);//一些有趣的dom操作。这个。应用题(dom,theme);},applyTheme(dom,theme){/*这里的附加逻辑*/},};</code></pre>
        <p>在我们的PUB模板中,我们有一些代码看起来像这样:</p>
        <pre><code class="language-HTML/JavaScript">// index.html <div id =“popover-dom”> </ div> <script> legacypover.init({//↓↓↓神奇可用↓↓↓用户数据!yay主题:“%{user.preferences。主题}” });</ script></code></pre>
        <p>虽然没有一个一刀切的解决方案来迁移像这样的东西,但我确实找到了一种在95%的情况下都能很好地工作的方法,在这里和那里做了一些调整。同样,这里的目标通常是尽可能少地修改原始代码,以减少潜在bug的表面积。</p>
        <p>我可以采取两种方法,如下所示:我可以选择以编程方式创建任何遗传效果所需的DOM<code>document.createElement</code>;或者我可以传递对从其他地方获取的DOM节点的引用。我通常会根据需要创建的DOM的复杂程度来决定使用哪种方法。在这种情况下,我将选择参考方法。结果如下所示:</p>
        <pre><code class="language-JavaScript">// letacy_popover.js const legacypopover = {init({dom,主题}){//一些有趣的DOM操纵。这个.ApplyTheme(DOM,主题);},ApplicateMe(DOM,主题){/ *附加逻辑* /},};导出默认legacypopover;</code></pre>
        <p>那么,这家公司在哪里呢<code>dom</code>节点来自?当然是响应!</p>
        <pre><code class="language-JSX">// popover.jsx导入从“反应”反应;导入来自“Lodash / Get”;从“GraphQL-Tag”导入GQL;从“@ apollo / ract-hooks”导入{imemerquery};从“./legacy_popover”导入legacypopover;// apollo查询以获取必需的用户数据。const theme_query = gql`查询getUsertheme($ userid:string!){user(id:$ userid){id首选项{theme}}}`;const popover = ract.memo(({userid})=> {//加载用户主题首选项。const {data} = uderquery(theme_query,{userid}); const主题= get(data,“user.preferences.theme”); // Keep a reference to the DOM node. const domRef = useRef(null); useEffect(() => { // Early exit if we’re not ready. if (!domRef.current || !theme) { return; } // Initialize the LegacyPopover. const dom = domRef.current; LegacyPopover.init({ dom, theme }); }, [theme, domRef]); return ( <div id=“popover-dom” ref={domRef} /> ); }); export default Popover;</code></pre>
        <p>这里有几件事要注意到。首先,遗留工具(导入为<code>LegacyPopover</code>) doesn’t get initialized until both the data it needs has loaded and the DOM node has rendered. Second, the initialization should only happen once per mount of the component (unless the<code>主题</code>应该改变),由于方式<code>useEffect</code>works. And finally, I memoize the component to avoid unnecessary re-renders that could mess with the DOM.</p>
        <p>有几个偶然的遗留实用程序我正在迁移一些导航逻辑。在水疗中心内,最好使用React Router而不是Via Anchor标签进行导航,因为这些不是给我们我们的好转换。为了完成这一点,我会通过遗留实用程序<code>goToUrl</code>函数作为参数,可以通过React Router的导航<code>历史记录.推送</code>在水疗中心内或手动设置<code>窗口位置</code>在非温泉环境中。</p>
        <h3 id="what-s-next">下一步是什么?</h3>
        <p>虽然大多数OKCupid网站现在正在188bet金宝搏官网通过我们的新单页应用程序架构提供服务,但仍有很多工作要做,并且我们迁移时探索了很多捆绑优化。其中,我们希望在大多数页面上搬到GraphQL,以便我们可以在我们的产品中共享一个标准化的实体存储(即,在页面上共享缓存数据)。此外,我们已经在考虑到渐进的Web应用技术,如服务工作人员,以便是长期本地缓存资源,并继续优化我们的产品,以实现更多的连接稀缺环境。我们相信这一新架构将使我们能够更快地移动并将新功能更加持续到我们的用户。至于我个人,我期待着回去删除许多人的遗留码。</p>
        <figure class="kg-card kg-image-card">
         <img src="//www.bizviewz.com/content/images/2020/02/giphy--5-.gif" class="kg-image" alt>
        </figure>
        <h4 id="interested-in-the-challenges-the-web-team-at-okcupid-is-working-on-we-re-hiring-">对Okcupid的Web团队的挑战感兴趣?188bet金宝搏官网<a href="https://okcupid.com/careers">We're hiring!</a></h4>
       </div>
      </section>
     </article>
    </div>
   </main>
   <aside class="read-next outer">
    <div class="inner">
     <div class="read-next-feed">
      <article class="read-next-card">
       <header class="read-next-card-header">
        <h3><span>更多</span><a href="//www.bizviewz.com/tag/web/">网状物</a></h3>
       </header>
       <div class="read-next-card-content">
        <ul>
         <li><h4><a href="//www.bizviewz.com/why-we-decided-against-graphql-for-local-state-management/">118金宝app
</a></h4>
          <div class="read-next-card-meta">
           <p><time datetime="2020-08-20">20 Aug 2020</time>- 阅读9分钟</p>
          </div></li>
        </ul>
       </div>
       <footer class="read-next-card-footer">
        <a href="//www.bizviewz.com/tag/web/">1个帖子→</a>
       </footer>
      </article>
      <article class="post-card post tag-customer-support ">
       <a class="post-card-image-link" href="//www.bizviewz.com/the-washing-your-hands-guide-for-your-mind-essential-steps-to-staying-healthy-while-working-from-home/"><img class="post-card-image" srcset="/content/images/size/w300/2021/03/ioana-tabarcea-mxwEAI4pTGU-unsplash.jpg 300w,
                    /content/images/size/w600/2021/03/ioana-tabarcea-mxwEAI4pTGU-unsplash.jpg 600w,
                    /content/images/size/w1000/2021/03/ioana-tabarcea-mxwEAI4pTGU-unsplash.jpg 1000w,
                    /content/images/size/w2000/2021/03/ioana-tabarcea-mxwEAI4pTGU-unsplash.jpg 2000w" sizes="(max-width: 1000px) 400px, 700px" loading="lazy" src="//www.bizviewz.com/content/images/size/w600/2021/03/ioana-tabarcea-mxwEAI4pTGU-unsplash.jpg" alt="为你的思想洗手指南"></a>
       <div class="post-card-content">
        <a class="post-card-content-link" href="//www.bizviewz.com/the-washing-your-hands-guide-for-your-mind-essential-steps-to-staying-healthy-while-working-from-home/">
         <header class="post-card-header">
          <div class="post-card-primary-tag">
           客户支持</div>
          <h2 class="post-card-title">为你的思想洗手指南</h2>
         </header>
         <section class="post-card-excerpt">
          <p>在家里工作时保持精神健康的基本步骤。与Covid-19强迫社会疏远,第一次在家工作的许多人正在努力实现</p>
         </section></a>
        <footer class="post-card-meta">
         <ul class="author-list">
          <li class="author-list-item">
           <div class="author-name-tooltip">
            爱丽丝Goguen Hunsberger.</div><a href="//www.bizviewz.com/author/alice/" class="static-avatar"><img class="author-profile-image" src="https://www.gravatar.com/avatar/863a49c515e4422eec487b2e3787086a?s=250&d=mm&r=x" alt="爱丽丝Goguen Hunsberger."></a></li>
         </ul>
         <div class="post-card-byline-content">
          <span><a href="//www.bizviewz.com/author/alice/">爱丽丝Goguen Hunsberger.</a></span>
          <span class="post-card-byline-date"><time datetime="2020-03-23">2020年3月23日</time><span class="bull">•</span>8 min read</span>
         </div>
        </footer>
       </div>
      </article>
      <article class="post-card post ">
       <a class="post-card-image-link" href="//www.bizviewz.com/monitoring-performance-the-easy-way/"><img class="post-card-image" srcset="/content/images/size/w300/2020/01/loading-1.png 300w,
                    /content/images/size/w600/2020/01/loading-1.png 600w,
                    /content/images/size/w1000/2020/01/loading-1.png 1000w,
                    /content/images/size/w2000/2020/01/loading-1.png 2000w" sizes="(max-width: 1000px) 400px, 700px" loading="lazy" src="//www.bizviewz.com/content/images/size/w600/2020/01/loading-1.png" alt="Monitoring Performance the Easy Way: HAProxy to the Rescue"></a>
       <div class="post-card-content">
        <a class="post-card-content-link" href="//www.bizviewz.com/monitoring-performance-the-easy-way/">
         <header class="post-card-header">
          <h2 class="post-card-title">Monitoring Performance the Easy Way: HAProxy to the Rescue</h2>
         </header>
         <section class="post-card-excerpt">
          <p>监控是一个基本的难题,这很烦人,因为它听起来应该很容易。我们构建了这个服务或应用程序,现在我们要做的就是</p>
         </section></a>
        <footer class="post-card-meta">
         <ul class="author-list">
          <li class="author-list-item">
           <div class="author-name-tooltip">
            Sammy Tbeile</div><a href="//www.bizviewz.com/author/sammy/" class="static-avatar"><img class="author-profile-image" src="//www.bizviewz.com/content/images/size/w100/2020/02/sammy_tbeile.jpg" alt="Sammy Tbeile"></a></li>
         </ul>
         <div class="post-card-byline-content">
          <span><a href="//www.bizviewz.com/author/sammy/">Sammy Tbeile</a></span>
          <span class="post-card-byline-date"><time datetime="2020-01-06">2012年1月6日</time><span class="bull">•</span>6分钟读取</span>
         </div>
        </footer>
       </div>
      </article>
     </div>
    </div>
   </aside>
   <footer class="site-footer outer">
    <div class="site-footer-content inner">
     <section class="copyright">
      <a href="//www.bizviewz.com">188bet金宝搏app</a>© 2021</section>
     <nav class="site-footer-nav">
      <a href="//www.bizviewz.com">最新的帖子</a>
      <a href="https://www.facebook.com/okcupid" target="_blank" rel="noopener">Facebook</a>
      <a href="https://twitter.com/okcupid" target="_blank" rel="noopener">推特</a>
      <a href="https://ghost.org" target="_blank" rel="noopener">鬼魂</a>
     </nav>
    </div>
   </footer>
  </div>
  <!-- TO ADD MORE LANGUAGES GO HERE: https://cdnjs.com/libraries/prism -->
  <!-- Main script -->
  <!-- Copy to clipboard -->
  <!-- Go lang -->
  <!-- Java -->
  <!-- Kotlin -->
  <!-- Python -->
  <!-- Swift -->
 </body>
</html>