Turbo

repository·main·Indexed 11 days ago

https://github.com/hotwired/turbo

A core part of the Hotwire suite that enables fast, interactive web applications by sending HTML over the wire instead of JSON. It provides mechanisms for accelerated navigation (Turbo Drive), scoped page updates (Turbo Frames), real-time content streaming (Turbo Streams), and native mobile app integration (Turbo Native). Version 8.0.23.

Tokens
9.8K
Snippets
40
Records
48
Agent score
93%

What's inside Turbo

  1. Overview of Turbo features

    main

    Turbo is a library designed to reduce the amount of custom JavaScript required in web applications by sending HTML over the wire. It provides four primary mechanisms for enhancing web and mobile experiences:

    • Turbo Drive: Accelerates navigation by intercepting link clicks and form submissions to replace full page reloads with AJAX requests, updating the browser history without a complete refresh.
    • Turbo Frames: Allows you to decompose a page into independent, scoped contexts. Navigation within a frame is restricted to that frame, and frames can be lazily loaded.
    • Turbo Streams: Delivers targeted page changes (via WebSocket or in response to form submissions) using HTML and a set of CRUD-like actions.
    • Turbo Native: Enables web applications to power native iOS and Android apps, providing seamless transitions between web content and native UI components.
  2. Access Turbo via the global window.Turbo object

    main

    The library automatically attaches itself to the global window.Turbo object. This object includes all core Turbo functionality, Turbo Elements, and StreamActions. This is useful for accessing Turbo from inline scripts or other libraries that do not use ES modules.

    // If Turbo.start() has been called, you can access it anywhere:
    console.log(window.Turbo);
    // Includes core methods, elements, and StreamActions
  3. Use Turbo custom elements

    main

    Turbo provides three primary custom elements for managing scoped navigation and real-time updates via WebSockets or Server-Sent Events:

    1. <turbo-frame>: Used to isolate parts of a page. Navigations within a frame are scoped to that frame, updating only its content.
    2. <turbo-stream>: Used to receive and apply incremental updates to the DOM (e.g., appending or replacing elements).
    3. <turbo-stream-source>: Used to establish a connection to a stream source (like a WebSocket or SSE endpoint) to receive <turbo-stream> updates.
    <!-- Example of the elements defined by Turbo -->
    <turbo-frame id="message_1">
      <!-- Content that can be updated independently -->
    </turbo-frame>
    
    <turbo-stream-source url="/cable" connected_callback="onConnected"></turbo-stream-source>
  4. Intercept and manage FetchRequest lifecycle via events

    main

    Turbo uses DOM events to allow developers to intercept and modify requests and responses.

    Request Interception

    Event: turbo:before-fetch-request (cancelable)

    • Purpose: Modify the request URL or options before the fetch is executed.
    • Detail:
      • fetchOptions: The options being sent to the fetch API.
      • url: The current URL of the request.
      • resume: A function to be called to continue the request if event.preventDefault() was called.

    Response Interception

    Event: turbo:before-fetch-response (cancelable)

    • Purpose: Intercept the response before it is processed by Turbo's success/failure handlers.
    • Detail:
      • fetchResponse: An instance of FetchResponse wrapping the native response.

    Error Handling

    Event: turbo:fetch-request-error (cancelable)

    • Purpose: Intercept errors occurring during the fetch process.
    • Detail:
      • request: The FetchRequest instance that errored.
      • error: The error object thrown.
  5. Initialize Turbo with Turbo.start()

    main

    To enable Turbo in your application, you must call Turbo.start(). This initializes the library, sets up polyfills, and attaches Turbo to the global window.Turbo object. Once started, Turbo will intercept link clicks and form submissions to perform fast, AJAX-driven page transitions.

    import * as Turbo from "@hotwired/turbo";
    
    Turbo.start();
  6. Register Turbo custom elements

    main

    The @hotwired/turbo package automatically defines the following custom elements if they are not already registered in the customElements registry:

    • turbo-frame (via FrameElement)
    • turbo-stream (via StreamElement)
    • turbo-stream-source (via StreamSourceElement)
  7. Use the <turbo-frame> custom element

    main

    <turbo-frame> is a custom element that contains a fragment of HTML which is updated based on navigation within it (e.g., via links or form submissions). When a link or form inside a <turbo-frame> is triggered, Turbo intercepts the request and updates only the content within that specific frame instead of performing a full page reload.

    Basic Usage

    <turbo-frame id="messages">
      <a href="/messages/expanded">
        Show all expanded messages in this frame.
      </a>
    
      <form action="/messages">
        Show response from this form within this frame.
      </form>
    </turbo-frame>
    <turbo-frame id="messages">
      <a href="/messages/expanded">
        Show all expanded messages in this frame.
      </a>
    
      <form action="/messages">
        Show response from this form within this frame.
      </form>
    </turbo-frame>
  8. Handle form submission lifecycle events

    main

    Turbo dispatches events during the form submission lifecycle that you can listen to. This is useful for updating UI, showing loading indicators, or performing cleanup.

    Key events include:

    • turbo:submit-start: Dispatched when the submission begins. The detail object contains the target (the form) and the formSubmission instance.
    • turbo:submit-end: Dispatched when the submission finishes (whether it succeeded, failed, or errored). The detail object contains the target, the formSubmission instance, and the result (which includes success and fetchResponse or error).

    Additionally, if you are implementing a custom delegate for FormSubmission, you can hook into methods like formSubmissionStarted, formSubmissionSucceededWithResponse, formSubmissionFailedWithResponse, formSubmissionErrored, and formSubmissionFinished.

    // Listening to Turbo events
    document.addEventListener("turbo:submit-start", (event) => {
      console.log("Form is submitting:", event.detail.target);
    });
    
    document.addEventListener("turbo:submit-end", (event) => {
      if (event.detail.success) {
        console.log("Submission succeeded!");
      } else {
        console.error("Submission failed:", event.detail.error);
      }
    });
  9. Configure form submission confirmation

    main

    Turbo allows you to intercept form submissions to show a confirmation dialog. By default, it looks for a data-turbo-confirm attribute on the submitter (e.g., a button) or the form itself.

    You can customize this behavior by providing a custom function in the Turbo configuration via config.forms.confirm. This function should accept the confirmation message, the form element, and the submitter, and return a Promise that resolves to a boolean.

    If no custom function is provided, Turbo defaults to using the native browser confirm() method.

    // Example of overriding the confirmation logic
    // Note: This assumes access to the Turbo config object
    config.forms.confirm = async (message, form, submitter) => {
      // Use a custom UI modal instead of browser confirm()
      return await myCustomModal.show(message);
    };
  10. Configure @web/test-runner via web-test-runner.config.mjs

    main

    The @web/test-runner configuration file defines how tests are executed, which browsers are used, and how files are processed. Key configuration properties include:

    • browsers: An array of browser launchers (e.g., using playwrightLauncher).
    • browserStartTimeout: The maximum time (in milliseconds) to wait for a browser to start.
    • nodeResolve: A boolean indicating whether to resolve Node.js modules.
    • files: A glob pattern specifying the test files to run.
    • testFramework: Configuration for the testing framework being used (e.ably providing a config object).
    • plugins: An array of plugins to extend the runner's functionality (e.g., esbuildPlugin for transpilation).
    import { esbuildPlugin } from '@web/dev-server-esbuild'
    import { playwrightLauncher } from '@web/test-runner-playwright'
    
    /** @type {import("@web/test-runner").TestRunnerConfig} */
    export default {
      browsers: [
        playwrightLauncher({
          product: 'chromium',
          launchOptions: {
            timeout: 60000
          }
        })
      ],
      browserStartTimeout: 600000,
      nodeResolve: true,
      files: "./src/tests/unit/**/*_tests.js",
      testFramework: {
        config: {
          ui: "tdd"
        }
      },
      plugins: [
        esbuildPlugin({ ts: true, target: "es2020" })
      ]
    }
  11. Configure Turbo via the config object

    main

    While some top-level setter functions exist, they are deprecated. You should use the config object for configuration.

    Deprecated Setters (Use config instead):

    • setProgressBarDelay(delay) $\rightarrow$ config.drive.progressBarDelay = delay
    • setConfirmMethod(confirmMethod) $\rightarrow$ config.forms.confirm = confirmMethod
    • setFormMode(mode) $\rightarrow$ config.forms.mode = mode
    import { config } from "@hotwired/turbo"
    
    config.drive.progressBarDelay = 300
    config.forms.confirm = (message) => window.confirm(message)
    config.forms.mode = "ajax"
  12. Configure Playwright test environment

    main

    The playwright.config.js file defines the execution environment for Turbo's functional and integration tests. It specifies the test directory, matching patterns, global timeouts, and the local web server required to run tests.

    Key configuration areas include:

    • Test Discovery: Tests are located in ./src/tests/ and must match the regex /(functional|integration)/.*_tests\.js/.
    • Timeouts: Global timeout and browserStartTimeout are set to 10000ms.
    • Retries: Failed tests are retried up to 2 times.
    • Base URL: All requests use http://localhost:9000/ as the base.
    • Web Server: The test suite automatically starts a local server using yarn start at http://localhost:9000/src/tests/fixtures/test.js.
    const config = {
      testDir: "./src/tests/",
      testMatch: /(functional|integration)/.*_tests\.js/,
      timeout: 10000,
      browserStartTimeout: 10000,
      retries: 2,
      use: {
        baseURL: "http://localhost:9000/"
      },
      webServer: {
        command: "yarn start",
        url: "http://localhost:9000/src/tests/fixtures/test.js",
        timeout: 10000,
        reuseExistingServer: !process.env.CI
      }
    }