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

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

特别是当您在处理成熟的应用程序时,占有数百万用户和大量的良好理解的代码。它是可以理解的,为什么这不谈到太多的大多数声乐科技公司往往落入两类之一:要么是一个相对较新的启动,都有比例的技术债务;或者他们是一个更建立的Faang型,几乎无限的资源来解决技术债务。

在Ok188bet金宝搏官网cupid,我们自2004年以来一直存在,具有相当小的(〜30人)工程团队在整个方面发电。我们的Web CodeBase将Ruby上的Ruby预测,您可能会感到惊讶(或适当地恐怖),以了解我们的技术堆栈依赖于我们呼叫酒吧的服务器端渲染的家庭成长的PHP样框架。我们的产品是最近,Pub,Vanilla JS,JQuery,CoffeScript,React,Reftux,Redux等,大部分地依赖于服务器端水合状态,代替适当的API。此外,有一个无数的可能死的代码路径,有条件地注入了可疑用品的第三方脚本。所以,当我们谈论遗产代码和技术债务时,这就是我们带到桌子的东西。

正如你所以,我们常常难以为产品的全锻作家进行商业理由。那么你如何拍摄比某些JavaScript程序员年龄较大的Web应用程序,并将其带入现代时代?好吧,我们现在实际上已经在这一目标方面取得了重大进展。事实上,反应很容易渲染到现有的应用程度至关重要,帮助我们早在2016年中期 - 今天我们的产品很快就是几乎完全反应所能的。我也很高兴地报告,我们所有的产品页面都是API驱动的(尽管我们不完全在GraphQL动力的乌托邦,我们稍后会争取更多)。这两个都是我们多年工作的缓慢,渐进的过渡,这使得我们的最终转向水疗中心明显更容易。

但尽管所有这些都取得了巨大的进步,但我们最近仍然依赖于Pub来实际上,每次请求都能使用每次申请来生成我们的每个HTML页面。这阻止了我们做冷酷的事情,就像没有我们的程序员必须学习拟订语言(非常有用的招聘)。因此,大约6个月前我开始在一个项目上移动我们的酒吧。我们使用我们的移动网络产品开始此转换,并将这些学习带到桌面3月后。两个平台的任务列表广泛但相当简单:

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

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

追踪死亡代码

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

甜美,甜美。

有很多代码,我偶然发现了我的工程侵权队已经有了远见,以便将分析事件添加到。当我找到这些时,我会检查他们是否在去年左右被解雇,并在什么卷中被解雇,并判断判断是否可以切除违规代码。这位诚实让我想向我曾写的每个反应组件添加分析事件。有人谈到我。

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

优化第一个油漆

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

// index.js  - 我们的主要应用进入点导入从“反应”反应;从“反应 -  DOM”的进口反应组;从“./api”导入api;导入助手来自“./helpers”;从“./app”导入应用程序;从“./app_error”导入apperror;const root = document.getElementById(“root”);//加载绝对必需品。api.loadCriticalData().then(()=> elplers.initializeglobals()).then(()=> acctdom.render(,root)).catch((错误)=> acctdom.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>反应最终将转变为“根”<code>div</code>,并消除它内部的一切;直到它发生,我们可以在那里提供任何东西<code>div</code>我们想要的,包括应用程序shell,以便在此期间显示用户或<code>题词</code>使用UI在没有JavaScript的情况下向用户展示。这可确保在JavaScript上加载线路,解析,编译,然后加载所需的数据时,用户不会被迫坐在空白屏幕前。<a href="https://medium.com/reloading/javascript-start-up-performance-69200f43b201">在此处了解有关JavaScript启动性能的更多信息。</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">
        </figure>
        <p>确保快速第一款油漆的另一个关键方面是确保我们的JavaScript捆绑在我们的单页应用程序添加了越来越多的路由时,我们的JavaScript捆绑在大​​小上没有气球。幸运的是,这些天,React提供了一些很棒的开箱工具,以防止这种情况发生。即:<code>反应.Lazy.</code>和<code>React.Suspense.</code>。</p>
        <p>如果你不熟悉<code>反应.Lazy.</code>,它是最近发布的一个很棒的补充<code>React@16.0.</code>,它允许我们按需动态加载任何组件。您不再需要第三方库到基于当前路由的代码拆分;相反,你可以做到这样的事情:</p>
        <pre><code class="language-JSX">// routes.jsx导入从“React”反应;从“React-Router-DOM”导入{Switch,Route};从“./loading”导入加载;//一些加载状态UI。const home = ract.laze(()=>导入(“页面/ home”));const login = ract.Lazy(()=>导入(“页面/登录”));const signup = ract.lazy(()=>导入(“页面/注册”));const路由=()=>(<ract.suspense alrack = {<loading />}> <switch> <路由精确路径=“/”组件= {home} /> <路由精确路径=“/ login”组件={login} /> <路由精确路径=“/注册”组件= {注册} /> </ switch> </react.suspense>);</code></pre>
        <p>在这里,我正在使用反应路由器<code>开关</code>和<code>路线</code>在给定路径处处理呈现组件的组件。它对它周围的懒惰加载逻辑完全不可知,您可以选择使用任何或任何没有库代替它。</p>
        <p>在这里发生了什么是<code>开关</code>组件将决定哪个<code>路线</code>基于当前路径呈现。当它找到一个匹配的时候<code>路线</code>将将组件传递给它<code>零件</code>支柱。路由器不知道的是,在这种情况下,每个组件都正在动态加载<em>只有在他们将被渲染之后</em>。<code>React.Suspense.</code>实际上会在渲染上暂停,直到组件负载。虽然它正在加载,但它将呈现给它的任何东西<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">
         <figcaption>
          <em>我不会假装我知道谷歌分析在这里做了什么。但是,真的?</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">// umpergoogleanalytics.js导入{useState,Usefeffect,Useref}从“反应”;从“./usescript”导入usersctift;const google_analytics_sdk_url =“//www.bizviewz.com/analytics.js”;/ ** *加载并使用Google Analytics SDK。* @param {object}位置 - 当前用户位置。* /函数umpergoogleanalytics(location = window.location){//跟踪加载状态。const [hasloaded,sethasloaded] = useState(false);//加载SDK。const sdkoptions = uereerf({onload:()=> sethasloaded(true)});符号(google_Analytics_sdk_url,sdkoptions.crord); // 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>这样,当我们需要使用它们时,它就像这样简单:</p>
        <pre><code>// Page.jsx导入{USElocation}来自“React-Router-DOM”;导入“./usegooglanalytics”的umlusoglanalytics;function basicpage(){const location = USELOCATION();umergoogleanalytics(位置);返回(<div> <header /> <main /> <footer /> </ div>);}</code></pre>
        <p>注意:我通过<code>位置</code>作为参数,而不是放置<code>USELOCATION.</code>呼叫钩子内。令人沮丧的是,我很早就发现了反应路由器的钩子如果你试图在a之外使用它们时会出现运行时错误<code>路由器</code>上下文,这已经证明了与我们的非水疗中心页面互操作性的麻烦。通过传递<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">迁移vanilla javascript工具</h3>
        <p>我们遇到的另一个问题是如何迁移我们的一些遗留UI工具,这些工具依赖于vanilla JavaScript和手动DOM操纵的组合。通常,我们有一些看起来像这样的东西:</p>
        <pre><code class="language-JavaScript">// Legacy_popover.js const legacypoper = {init({theme}){const dom = document.getElementById(“Popover-Dom”);//一些有趣的DOM操纵。这个.ApplyTheme(DOM,主题);},ApplicateMe(DOM,主题){/ *附加逻辑* /},};</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%的情况下工作的方法,在这里和那里有一些调整。同样,这里的目标通常是尽可能地修改原始代码,以减少潜在错误的表面积。</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>)未初始化,直到它所需的数据加载并且DOM节点呈现。其次,初始化应该只发生一次组件一次(除非<code>主题</code>应该改变),由于方式<code>使用</code>作品。最后,我介绍了组件以避免可能与DOM混淆的不必要的重新渲染。</p>
        <p>有几个偶然的遗留实用程序我正在迁移一些导航逻辑。在水疗中心内,最好使用React Router而不是Via Anchor标签进行导航,因为这些不是给我们我们的好转换。为了完成这一点,我会通过遗留实用程序<code>gotourl.</code>函数作为参数,可以通过React Router的导航<code>历史</code>在水疗中心内或手动设置<code>window.location.</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">
        </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">我们正在招聘!</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月20日20日</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 no-image no-image">
       <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分钟</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="监控性能简单的方法:Haproxy到救援"></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">监控性能简单的方法:Haproxy到救援</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>
 </body>
</html>