turbolinksソース分析(回転)

6859 ワード

Turbolinks 5はCoffeescriptで記述する.
Turbolinks 5を学ぶことで
  • ブラウザがウェブページをロードする際の処理フロー
  • を根本的に把握する.
  • Turbolinks 5の核心原理を掌握して、どのように1つの“大きい”の先端プロジェクト
  • をモジュール化することをマスターします
  • 老鳥学会に従ってソースコード
  • を分析する方法
    準備作業
    cloneプロジェクト:git clone https://github.com/turbolinks/turbolinksエディタを用意してatomまたはsublime textをお勧めします
  • Turbolinksが見つかりました.start()入口
  • 老鳥は、コードを研究する前に、あなたの研究対象の適用範囲を明確にすることが非常に重要で、大部分の時間はまずドキュメントを見ることが非常に効果的にプロジェクトアーキテクチャを熟知する手段であることを示唆した.
  • ですので、事前にREADMEを読むことをお勧めします.

  • スタート
    入り口はとても簡単です.
    Turbolinks.start = ->
    
      if installTurbolinks()
    
        Turbolinks.controller ?= createController()
    
        Turbolinks.controller.start()
    
    installTurbolinks = ->
    
      window.Turbolinks ?= Turbolinks
    
      moduleIsInstalled()
    
    createController = ->
    
      controller = new Turbolinks.Controller
    
      controller.adapter = new Turbolinks.BrowserAdapter(controller)
    
      controller
    
    moduleIsInstalled = ->
    
      window.Turbolinks is Turbolinks
    
    Turbolinks.start() if moduleIsInstalled()
    

    次の情報が表示されます.
  • Turbolinksはグローバルwindowにマウントされます.Turbolinksオブジェクト、単一の例(すなわちグローバルに1つしかない).
  • Turbolinks.Controllerはコアであり、単例でもある(グローバルには1つしかない).
  • Turbolinks.controller.start()は本物の入り口です.

  • 老鳥は、静的なコードから空間想像力で実行時の各クラスやコンポーネントの関係を抽出することを提示し、これはコードを読む精粋である.必要に応じて、動的なdebugツールを用いて動的解析を行うことができる.
    コントロールは何をしていますか?
    コントローラに飛び込みましたcoffee、start()メソッドを見つけます:
      start: ->
    
        unless @started
    
          addEventListener("click", @clickCaptured, true)
    
          addEventListener("DOMContentLoaded", @pageLoaded, false)
    
          @scrollManager.start()
    
          @startHistory()
    
          @started = true
    
          @enabled = true
    

    非中堅コードには関心がないが、最も重要なエントリはすでに現れている:clickCaptured関数はグローバルなclickイベントにマウントされ、pageLoaded関数はDOMContentLoadedイベントDOMContentLoadedとLoadイベントの違いは前者がcssを待たず、imageロードが完了するとトリガされ、後者などのページが完全にロードされた後にトリガされることである.
    ここから、最初の重要な実装を見ました.
    a要素のイベントをバインドする方法:addEventListener
    この時私たちはもうvisit()という入り口に着いた.下を見続けます.
    visit() -> @adapter.visitProposedToLocationWithAction -> @controller.startVisitToLocationWithAction
    

    最後にcontroller.に来ましたcoffeeのstartVisityToLocationWithAction:
      startVisit: (location, action, properties) ->
    
        @currentVisit?.cancel()
    
        @currentVisit = @createVisit(location, action, properties)
    
        @currentVisit.start()
    
        @notifyApplicationAfterVisitingLocation(location)
    

    次の情報が表示されます.
  • ユーザがリンクをクリックすると、実際にTurbolinks 5はVisitのインスタンスを作成して呼び出す.start()は、具体的なアクセスプロセスを開始する.

  • この場合もcontrollerの役割を基本的に分析することができます.
  • controllerは、各モジュールを関連付けるすべての関連クラスのコンテナですが、Visitは例外であり、アクセスするたびに新しいインスタンスが生成され、@currentVisit
  • に格納されます.
    Visitの本格的なアクセス
    start() -> @adapter.visitStarted(this) -> visit.issueRequest(); visit.changeHistory(); visit.loadCachedSnapshot()
    

    これは真の処理プロセスです.
  • HTTP Request(非同期、Javascriptでのリクエストはデフォルトで非同期)
  • を送信
  • ブラウザ履歴の更新(History API経由)
  • cacheページ
  • をロード
    別れる時だ.
    HttpRequest
    http_request.coffeeでは、HTTP Requestがどのように送信されるかを検討します.
      createXHR: ->
    
        @xhr = new XMLHttpRequest
    
        @xhr.open("GET", @url, true)
    
        @xhr.timeout = @constructor.timeout * 1000
    
        @xhr.setRequestHeader("Accept", "text/html, application/xhtml+xml")
    
        @xhr.setRequestHeader("Turbolinks-Referrer", @referrer)
    
        @xhr.onprogress = @requestProgressed
    
        @xhr.onload = @requestLoaded
    
        @xhr.onerror = @requestFailed
    
        @xhr.ontimeout = @requestTimedOut
    
        @xhr.onabort = @requestCanceled
    

    案の定、XMLHttpRequestオブジェクトを介して、非同期リクエストが開始された.コールバックが設定されている.私たちはしばらく異常処理を見ないでrequestLoadedを直接見ます.
      requestLoaded: =>
    
        @endRequest =>
    
          if 200 <= @xhr.status < 300
    
            @delegate.requestCompletedWithResponse(@xhr.responseText, @xhr.getResponseHeader("Turbolinks-Location"))
    
          else
    
            @failed = true
    
            @delegate.requestFailedWithStatusCode(@xhr.status, @xhr.responseText)
    

    @delegateって何?実際、delegateの命名はフレームワークの中で非常によく見られ、代理人を代表して、要求を対応するインタフェースに転送する.ここで明らかに従来のVisit例である.このような設計により、HttpRequestオブジェクトを特定の実装クラス(例えばvisit)に依存することなく、より汎用的にすることができる.
    解析を続けると、最後に呼び出されたことがわかります.
      loadResponse: ->
    
        if @response?
    
          @render ->
    
            @cacheSnapshot()
    
            if @request.failed
    
              @controller.render(error: @response, @performScroll)
    
              @adapter.visitRendered?(this)
    
              @fail()
    
            else
    
              @controller.render(snapshot: @response, @performScroll)
    
              @adapter.visitRendered?(this)
    
              @complete()
    

    これが最終HttpRequest以降の動作であり,@controller.が呼び出されていることがわかる.renderインタフェースレンダーに行かないで前の分岐点に戻ります.
    老鳥は、良い命名はコードを読む作業量を極めて低減することができ、最後まで追いかけないで、一つのインタフェースの意味を明確にした後、他の重要なインタフェースに分析することができるとヒントを与えた.例えばrenderは非常に明確な意味であり、私たちはほとんど分析しなくてもその役割を理解することができます.
    loadCachedSnapshot
    
      loadCachedSnapshot: ->
    
        if snapshot = @getCachedSnapshot()
    
          isPreview = @shouldIssueRequest()
    
          @render ->
    
            @cacheSnapshot()
    
            @controller.render({snapshot, isPreview}, @performScroll)
    
            @adapter.visitRendered?(this)
    
            @complete() unless isPreview
    

    素晴らしいですね.cache pageの最終ロードも@controller.を通ります.renderが行いました.
    最終的には最も重要なrender関数に入る必要があります
    controller.coffee
      render: (options, callback) ->
    
        @view.render(options, callback)
    

    ビューに入ります.coffee
      renderSnapshot: (snapshot, callback) ->
    
        Turbolinks.SnapshotRenderer.render(@delegate, callback, @getSnapshot(), Turbolinks.Snapshot.wrap(snapshot))
    
    

    snapshot_に入りますrenderer.coffee
      render: (callback) ->
    
        if @trackedElementsAreIdentical()
    
          @mergeHead()
    
          @renderView =>
    
            @replaceBody()
    
            @focusFirstAutofocusableElement()
    
            callback()
    
        else
    
          @invalidateView()
    

    私たちは一周して、最終的にrenderの実際の入り口を見つけました.renderが以下のことをしたのを見ました.
  • 連結ヘッド
  • body
  • を置き換える
  • その他
  • マーガレットヘッドを見続けて
      mergeHead: ->
    
        @copyNewHeadStylesheetElements()
    
        @copyNewHeadScriptElements()
    
        @removeCurrentHeadProvisionalElements()
    
        @copyNewHeadProvisionalElements()
    

    非常に明らかな命名
    私たちはheadから続けます.details.coffeeでは、特定の操作を分析します.
    document.head.appendChild(element)
    

    すなわち、mergeHeadは、ヘッダ情報を同期してロード.ここではJavascript操作scriptタグ要素の役割を明確に理解する.(srcプロパティの内容を自動的に非同期で取り戻して実行します)
    同様に、replaceBodyの操作の鍵は:
        for replaceableElement in @getNewBodyScriptElements()
    
          element = @createScriptElement(replaceableElement)
    
          replaceableElement.parentNode.replaceChild(element, replaceableElement)
    

    非常に明確な命名で、ここの論理をすぐに理解することができます.
    原著--深セン市80パーセント科学技術有限会社李亜飛