多年以来,网络社会的广泛赞誉单分页应用(SPA)架构的优势。去到任何网络会议或流行的科技博客,你一定会遇到对自己利益的讨论过多。这是有道理的,为什么我们如此放进去:我们每次浏览到一个网站的不同页面时速度很慢等待加载并初始化HTML,JavaScript的一个整版,和样式;按需加载少量的JavaScript脚本快。水疗也让我们避免可怕的闪存的空白屏幕页面加载之间,而不是让我们提供更人性化的加载屏幕。这使得水疗更适合比对于像移动网络,或者在某些国家甚至桌面Web连接稀缺的环境下传统架构。如果正确地进行了优化,即使是第一页负载可以是相当瘦;而我甚至不会进入知名金融奖励提供一个快速的浏览体验。简短的版本是:总得走的快

不过,现在很多SPA的讨论没有考虑到的是它可以只是一个多么艰巨的过程迁移到一个单一的页面架构。

这尤其是当你面对一个成熟的应用,数以百万计的用户和难溶了解码堆。这是可以理解为什么这个就不讲太多,许多最激烈的高科技公司一般分为两类:要么他们是一个相对较新的启动,有小比例的债务高科技;或者他们是更成熟舫型与近乎无限的资源来解决债务高科技。

在Ok188bet金宝搏官网Cupid,我们自2004年以来一直围绕,在整个一个相当小的(〜30ish人)的工程团队供电东西。我们的网页代码库早Ruby on Rails的,你可能会感到惊讶(或适当吓坏了)来学习我们的技术栈依赖于一个土生土长的PHP类框架服务器端渲染,我们称之为酒吧。我们的产品是,直到最近,酒吧的错落有致,香草JS,jQuery的,Coffescript,反应,回流,终极版,多,很大程度上依赖于服务器端的水合状态在正确的地方的API。此外,还有一种可能死的代码路径万千,并质疑是否有用的有条件注入第三方脚本。所以,当我们谈论的遗留代码和技术的债务,这是我们带来什么表。

正如大家都知道,它往往很难对产品的完全重写一个商业理由。那么,你如何采取比一些JavaScript程序员旧的Web应用程序,并把它纳入新时代?好了,我们实际上已经使朝着这个目标有一段显著的进步了。该阵营可以轻松地呈现到现有的应用程序,这一事实是帮助我们开始采用它的关键,早中期的2016,今天我们的产品是值得庆幸的是几乎完全反应动力。我也很高兴向大家报告,我们所有的产品网页都是API驱动(虽然我们在GraphQL供电乌托邦是不太我们走向,后来更多正在奋斗)。这两种都是缓慢的,渐进的转变,我们已经在工作了几年了,这让我们最终移动到SPA显著容易。

但是,尽管所有这些巨大的进步,我们是,直到最近仍依靠酒吧实际生成的每个HTML页面中对每个请求的飞行。这是阻止我们做很酷的事情,好像没有我们的程序员需要学习由语言(招募非常有用)。因此,大约6个月前,我开始一个项目,推动我们关闭酒吧的。我们开始与我们的移动网络产品的这种转变,并把学习收获那些桌面网络3个月后。这两个平台的任务列表是广泛的,但相当简单:

  • 在生成编译的HTML文件来服务我们的JavaScript包。
  • 确保快速(足够的)第一油漆。
  • 使用代码分裂,以避免臃肿束结束了。
  • 寻求更好的抽象像第三方脚本样板逻辑。
  • 更新传统香草JavaScript工具依赖于直接的DOM操作的反应。
  • 消除任何未使用的代码,我发现沿途。

在这个庞大的迁移过程中,我做了一些意见,并设计了一个抽象的情侣,我希望你会发现有用的,无论是在清理旧代码,以及在编写新的代码。

追查死代码

因此,让我们开始在最合理的方式解决这些要点:按字母顺序排列。我们的传统基础设施的迁移时关闭,减少不必要的代码是一个巨大的目标。部分,这是理想主义(耶删除代码!),部分地,这是务实的(耶更少的代码翻译!)。但是,一个令人难以置信的恼人的挑战是什么之间的遗留代码是为了保持我们的网站工作的关键雪亮,哪些代码可以安全地删除,因为它没从当OkCupid主办杂志和论坛的日子里(是的,我们用来做相关188bet金宝搏官网;不,我不能告诉你为什么我们认为这是一个好主意)。有,但是,在我们的工具带一个非常基本的工具,证明搞清楚什么代码正在运行宝贵的,什么是不:分析!

甜,甜沉默。

有大量的代码,我偶然发现了我的祖先工程有先见之明补充分析事件。当我发现这些,我会检查它们是否一直在过去一年左右的时间内发射,以及在什么音量,并作出判断呼叫为有问题的代码是否可以切除。老实说,这让我想添加一个分析事件,每一个阵营组成部分我曾经写。有人谈论我离开它。

如果只有一切都那么简单,触发一个事件分析!

优化第一涂料

迁移到一个单一的网页应用程序的另一个困难的方面是,我们的很多依赖于某些数据的Web应用程序在我们的应用程序做出反应中的所有的时间被保证可用。虽然这很容易,以确保与服务器端的数据水化,这是移动到一个静态的HTML模板时更具挑战性。摆脱所有这些依赖的将是极其繁琐和困难的QA,所以我们必须制定战略,以解决该问题。最后,我们决定在东西是这样的:

// index.js  - 我们主要的应用程序入口点进口从“反应”反应;从进口ReactDOM “反应-DOM”;从 “./api” 进口API;从“./helpers”进口助手;导入应用从“./app”;进口AppError从 “./app_error”;常量根=的document.getElementById( “根”);//加载的绝对必需品。API.loadCriticalData()。然后(()=> Helpers.initializeGlobals())。然后(()=> ReactDOM.render(<应用/>,根)).catch((误差)=> ReactDOM.render(,根));

这里做的事情是,它加载这是我们的应用是绝对关键的数据。然后,它使数据提供给应用程序的其余部分初始化一些全局库。最后,它使应用程序的DOM。如果在这个过程中有什么要失败,我们退回到错误状态来代替。

还有一个显著下跌这种方法,虽然:什么获取呈现在我们等待的数据加载?我们不希望向用户显示一个空白的屏幕,而出现这种情况,即使它只每个会话发生一次,显示用户黑屏是永远不会理想。为了解决这个问题,我们来填充我们的应用程序壳模板文件:

// index.html的  188bet金宝搏官网 OkCupid </ TITLE> </ HEAD> <BODY> <DIV ID = “根”> <DIV类= “一些加载状态”> <DIV类=“一些-华而不实装载机”> </ DIV> </ DIV> <NOSCRIPT>打开JavaScript,混蛋!</ NOSCRIPT> </ DIV> </ BODY> </ HTML></code></pre>
        <p>反应将最终呈现到“根”<code>DIV</code>,并消灭它内部的一切;直到出现这种情况,我们可以渲染内的任何东西<code>DIV</code>我们希望,包括应用外壳,显示在此期间或用户<code>NOSCRIPT</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包没有在尺寸气囊作为我们的单页应用程序添加越来越多的路线。幸运的是,这些天,阵营提供了一些伟大外的现成工具来防止这种情况发生。即:<code>React.lazy</code>和<code>React.Suspense</code>。</p>
        <p>如果你不熟悉<code>React.lazy</code>,这是一个伟大的除了已发布的相当最近在<code>react@16.6.0</code>,它让我们以动态按需加载任何部件。你不再需要第三方库基于当前路线代码分割;相反,你可以这样做:</p>
        <pre><code class="language-JSX">// Routes.jsx进口从“反应”反应;进口{交换机,路由}从 “反应路由器-DOM”;从“./Loading”导入加载;//有些加载状态UI。常量首页= React.lazy(()=>导入( “页/家”));常量登录= React.lazy(()=>进口( “页/登录”));常量注册= React.lazy(()=>进口( “页/注册”));常量路线=()=>(<React.Suspense后备= {<装入/>}> <开关> <路线确切路径= “/” 成分= {HOME} /> <路线确切路径= “/登录” 成分={登录} /> <路线确切路径= “/注册” 成分= {}注册/> </开关> </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>起反应的GraphQL框架,已经实现了这一点),或其他任何异步操作发生。但今天它的稳定足以使用了代码分裂的生产,我们一样!<a href="https://reactjs.org/docs/code-splitting.html">了解更多关于代码分裂与这里作出反应</a>。</p>
        <h3 id="third-party-scripts-with-usescript">第三方脚本与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>我不会假装我知道谷歌Analytics(分析)在这里做。但是,真的吗?</em>
         </figcaption>
        </figure>
        <p>其中最常见的东西,我发现SPA迁移过程中自己做一些第三方脚本粘贴,几乎所有这些看起来是这样的:</p>
        <pre><code class="language-HTML/JavaScript"><! - 谷歌分析 - > <SCRIPT>(功能(I,S,O,G,R,A,M){I [ 'GoogleAnalyticsObject'] = R; I [R] = I [R] ||函数(){(I [R] .Q = I [R] .Q || [])推(参数)。},1 [R] .L = 1个*新日期(); A = s.createElement(○)中,m = s.getElementsByTagName(O)[0]; a.async = 1; a.src =克; m.parentNode.insertBefore(A,M)})(窗口,文件, '脚本',“HTTPS://www.bizviewz.com/analytics.js','ga');GA( '创建', 'UA-XXXXX-Y', '自动');GA( '发送', '网页浏览');</ SCRIPT> <! - 结束谷歌Analytics(分析) - ></code></pre>
        <p>这些通常会预精缩如上,但要点字面上每一个是:脚本标记添加到身体,加载从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">// useScript.js进口{useRef,useEffect}从“反应”;/ ** *动态加载的JavaScript。* @param {string}里的网址 - 脚本的URL来加载。* @参数{对象} [选项= {}]  - 为对脚本元素的任何覆盖。* /功能useScript(URL,选项= {}){//创建脚本元件,一次。常量scriptRef = useRef(使用document.createElement( “脚本”));useEffect(()=> {//基本设置常量脚本= scriptRef.current; script.type = “文本/ JavaScript的”; script.src = URL; //高级修饰,和/或订阅事件Object.keys(选项).forEach((键)=>脚本[键] =道具[键]); //如果有必要添加到身体如果(document.body.contains(脚本)!){document.body.appendChild(。脚本);}},[网址,选项]);}出口默认useScript;</code></pre>
        <p>由于这些脚本往往需要多一点的设置,我通常的做法是将它们包装,像这样:</p>
        <pre><code class="language-JSX">// useGoogleAnalytics.js进口{useState,useEffect,useRef}从“反应”;从“./useScript”导入useScript;常量GOOGLE_ANALYTICS_SDK_URL =“//www.bizviewz.com/analytics.js”;/ ** *加载并使用谷歌Analytics(分析)SDK。* @参数{对象}位置 - 当前用户位置。* /功能useGoogleAnalytics(位置= window.location的){//跟踪负载状态。常量[hasLoaded,setHasLoaded] = useState(假);//加载SDK。常量sdkOptions = useRef({的onload:()=> setHasLoaded(真)});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>这样,当我们需要使用它们,它是这样简单:</p>
        <pre><code>// Page.jsx进口{useLocation}从“反应路由器-DOM”;从“./useGoogleAnalytics”导入useGoogleAnalytics;功能BasicPage(){const的位置= useLocation();useGoogleAnalytics(位置);返回(<DIV> <页眉/> <主/> <页脚/> </ DIV>);}</code></pre>
        <p>注:我通过<code>位置</code>这里一个参数,而不是放置<code>useLocation</code>挂钩中调用。无奈的是,我发现很早就上作出反应,如果你尝试之外使用它们的路由器的钩子抛出一个运行时错误<code>路由器</code>背景,证明麻烦与我们的非SPA网页的互操作性。通过传递<code>位置</code>作为一个参数,而不是调用<code>useLocation</code>直接挂钩,我们能够更容易地重用它横跨两个SPA和非SPA代码。</p>
        <p>这种方法加载脚本尺度以及任何数量的辅助脚本的,而其余易于阅读的开发人员可以通过条码扫描!要了解更多关于阵营钩,<a href="//www.bizviewz.com/getting-hooked-on-react-hooks/">看到我的博客帖子较早的主题在这里</a>。</p>
        <h3 id="migrating-vanilla-javascript-tools">迁移香草JavaScript工具</h3>
        <p>我们遇到的另一个问题是如何迁移的一些上香草JavaScript和DOM手工操作的组合依靠我们的传统的UI工具。通常我们就会有一些工具,它看起来是这样的:</p>
        <pre><code class="language-JavaScript">// legacy_popover.js常量LegacyPopover = {的init({主题}){const的DOM =的document.getElementById(“酥料饼-DOM”);//一些有趣的DOM操作。this.applyTheme(DOM,主题);},applyTheme(DOM,主题){/ *附加逻辑这里* /},};</code></pre>
        <p>而我们的酒吧模板内,我们就会有一些代码,看起来像这样:</p>
        <pre><code class="language-HTML/JavaScript">// index.html <div id=“popover-dom”></div> <script> LegacyPopover.init({ // ↓↓↓ magically available ↓↓↓ user data! Yay  theme: “%{user.preferences.theme}” }); </script></code></pre>
        <p>虽然不是一个尺寸适合所有迁移这样的事情的解决方案,我发现,在95%的病例在这里工作很好,有一些调整,有一种方法。同样,这里的目标通常是修改的原始代码尽可能小,以减少潜在的bug的表面积。</p>
        <p>有两种方法,我可以拿的东西是这样的:我可以选择以编程方式创建通过所需的传统工具的任何DOM<code>使用document.createElement</code>;或我能通过从其它地方得到一个DOM节点的引用。我通常会决定与取决于需要创建的DOM多么复杂去哪个方法。在这种情况下,我会选择参考进场。结果看起来是这样的:</p>
        <pre><code class="language-JavaScript">// legacy_popover.js常量LegacyPopover = {的init({DOM,主题}){//一些有趣的DOM操作。this.applyTheme(DOM,主题);},applyTheme(DOM,主题){/ *附加逻辑这里* /},};出口默认LegacyPopover;</code></pre>
        <p>那么,这是否将<code>DOM</code>节点从何而来?反应,当然!</p>
        <pre><code class="language-JSX">// Popover.jsx进口从“反应”反应;从“lodash /获得”进口搞定;从“graphql标签”导入GQL;进口{useQuery}从“@阿波罗/反应钩”;进口LegacyPopover从“./legacy_popover“;//阿波罗查询来获取必要的用户数据。常量THEME_QUERY = gql`查询getUserTheme($用户名:字符串!){用户(ID:$用户id){ID喜好{主题}}}`;常量酥料饼= React.memo(({用户ID})=> {//加载用户主题偏好const的{}数据= useQuery(THEME_QUERY,{用户ID}); const的主题= GET(数据“,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>useEffect</code>作品。最后,我memoize的组件,以避免不必要的重新渲染,可以与DOM的混乱。</p>
        <p>有在传统工具,我迁移处理一些导航逻辑几次。内的SPA,这将是最好使用路由器做出反应,而不是通过锚标签,因为这些不给我们我们很好的过渡到导航。要做到这一点,我会通过传统的实用程序<code>goToUrl</code>函数作为参数,它要么通过导航路由器作出反应的<code>history.push</code>在SPA内或通过手动设定<code>window.location的</code>在非SPA环境。</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正在挑战?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">2020年8月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">
            萨米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="萨米Tbeile"></a></li>
         </ul>
         <div class="post-card-byline-content">
          <span><a href="//www.bizviewz.com/author/sammy/">萨米Tbeile</a></span>
          <span class="post-card-byline-date"><time datetime="2020-01-06">2020年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>©2020</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>