CDN Integration (No Build Tools)

Learn how to integrate the Tiro.health Web SDK using CDN links for quick prototyping and simple applications without build tools.

Overview

The CDN approach provides:

  • Zero build tools required
  • Quick prototyping for demos and testing
  • Simple HTML + JavaScript structure
  • Global variable access to SDK components
  • Fast iteration for learning and development

This approach is ideal for quick testing, demos, and educational purposes.


When to Use CDN

Good Use Cases

  • Quick prototyping and proof of concepts
  • Learning the SDK and experimenting
  • Simple demos and presentations
  • Internal tools with limited complexity
  • Testing integration before full implementation

When to Use NPM Instead

For production applications, use NPM-based integration:

  • Better tree-shaking and bundle optimization
  • Type safety with TypeScript
  • Dependency management with package.json
  • Version control and reproducible builds
  • Security updates through package manager

See React, Angular, or Vanilla JS integration guides for production-ready setups.


Quick Start

Minimal HTML Example

Create a single index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Patient Form - Tiro.health</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 2rem;
      background-color: #f5f5f5;
    }
    h1 {
      color: #333;
    }
    #form-container {
      background: white;
      padding: 2rem;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
  </style>
</head>
<body>
  <h1>Patient Questionnaire</h1>
  <div id="form-container"></div>

  <!-- Load Web SDK from CDN -->
  <script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>

  <script>
    // Access SDK through global TiroWebSDK variable
    const { FormFiller } = window.TiroWebSDK

    // Configuration
    const config = {
      questionnaire: 'your-questionnaire-id',
      sdcEndpoint: {
        resourceType: 'Endpoint',
        address: 'https://your-sdc-backend.example.com/fhir'
      },
      onSubmit: (response) => {
        console.log('Form submitted:', response)
        alert('Form submitted successfully!')
      },
      onChange: (response) => {
        console.log('Form changed:', response)
      },
      onError: (error) => {
        console.error('Form error:', error)
        alert('Error: ' + error.message)
      }
    }

    // Initialize when DOM is ready
    document.addEventListener('DOMContentLoaded', () => {
      const container = document.getElementById('form-container')
      const filler = new FormFiller(config)
      filler.mount(container)

      // Cleanup on page unload
      window.addEventListener('beforeunload', () => {
        filler.unmount()
      })
    })
  </script>
</body>
</html>

Open this file directly in your browser - no build step required!


Complete Example

Full-Featured Application

Complete example with all components and visualization:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Patient Forms - Tiro.health</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background-color: #f5f5f5;
      color: #333;
      line-height: 1.5;
    }

    .container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 2rem;
    }

    header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 2rem;
    }

    h1 {
      font-size: 2rem;
    }

    h2 {
      font-size: 1.5rem;
      margin-bottom: 1rem;
    }

    .layout {
      display: grid;
      grid-template-columns: 300px 1fr;
      gap: 2rem;
    }

    .sidebar {
      display: flex;
      flex-direction: column;
      gap: 1.5rem;
    }

    .main-content {
      display: flex;
      flex-direction: column;
      gap: 1.5rem;
    }

    .card {
      background: white;
      padding: 1.5rem;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }

    .loading,
    .error {
      text-align: center;
      padding: 3rem;
    }

    .error {
      color: #d32f2f;
    }

    .error button {
      margin-top: 1rem;
      padding: 0.5rem 1rem;
      background-color: #1976d2;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 1rem;
    }

    .error button:hover {
      background-color: #1565c0;
    }

    #content {
      display: none;
    }

    #content.show {
      display: block;
    }

    @media (max-width: 768px) {
      .layout {
        grid-template-columns: 1fr;
      }
      .container {
        padding: 1rem;
      }
    }
  </style>
</head>
<body>
  <div class="container">
    <!-- Loading State -->
    <div id="loading" class="loading">
      <h2>Loading...</h2>
    </div>

    <!-- Error State -->
    <div id="error" class="error" style="display: none;">
      <h2>Error</h2>
      <p id="error-message"></p>
      <button onclick="retryInitialization()">Retry</button>
    </div>

    <!-- Main Content -->
    <div id="content">
      <header>
        <h1>Patient Forms</h1>
        <div id="toggle-container"></div>
      </header>

      <div class="layout">
        <aside class="sidebar">
          <div class="card">
            <h2>Context</h2>
            <div id="context-container"></div>
          </div>

          <div class="card">
            <h2>Validation</h2>
            <div id="validation-container"></div>
          </div>
        </aside>

        <main class="main-content">
          <div class="card">
            <h2>Questionnaire</h2>
            <div id="form-container"></div>
          </div>

          <div class="card">
            <h2>Clinical Narrative</h2>
            <div id="narrative-container"></div>
          </div>
        </main>
      </div>
    </div>
  </div>

  <!-- Load Web SDK from CDN -->
  <script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>

  <script>
    // Global app state
    const app = {
      components: {},
      config: {
        sdcEndpoint: {
          resourceType: 'Endpoint',
          address: 'https://your-sdc-backend.example.com/fhir'
        },
        dataEndpoint: {
          resourceType: 'Endpoint',
          address: 'https://your-fhir-server.example.com/fhir'
        },
        questionnaireId: 'your-questionnaire-id',
        patientId: 'your-patient-id',
        visualize: true // Set to false for production
      }
    }

    // Initialize application
    function initializeApp() {
      try {
        showLoading()

        // Get SDK components from global
        const {
          FormFiller,
          LaunchContextProvider,
          Narrative,
          ValidationFeedback,
          VisualizationToggle
        } = window.TiroWebSDK

        // Create FormFiller
        app.components.filler = new FormFiller({
          questionnaire: app.config.questionnaireId,
          sdcEndpoint: app.config.sdcEndpoint,
          visualize: app.config.visualize,
          onSubmit: handleSubmit,
          onChange: handleChange,
          onError: handleError
        })

        // Create LaunchContextProvider
        app.components.contextProvider = new LaunchContextProvider({
          dataEndpoint: app.config.dataEndpoint,
          filler: app.components.filler,
          patientId: app.config.patientId
        })

        // Create Narrative
        app.components.narrative = new Narrative({
          filler: app.components.filler,
          visualize: app.config.visualize
        })

        // Create ValidationFeedback
        app.components.validation = new ValidationFeedback({
          filler: app.components.filler,
          visualize: app.config.visualize
        })

        // Create VisualizationToggle
        app.components.toggle = new VisualizationToggle()

        // Mount all components
        app.components.contextProvider.mount(
          document.getElementById('context-container')
        )
        app.components.filler.mount(
          document.getElementById('form-container')
        )
        app.components.narrative.mount(
          document.getElementById('narrative-container')
        )
        app.components.validation.mount(
          document.getElementById('validation-container')
        )
        app.components.toggle.mount(
          document.getElementById('toggle-container')
        )

        hideLoading()
      } catch (error) {
        console.error('Failed to initialize app:', error)
        showError(error)
      }
    }

    // Event handlers
    function handleSubmit(response) {
      console.log('Form submitted:', response)
      alert('Form submitted successfully!')
    }

    function handleChange(response) {
      console.log('Form updated:', response)
    }

    function handleError(error) {
      console.error('Form error:', error)
      showError(error)
    }

    // UI state management
    function showLoading() {
      document.getElementById('loading').style.display = 'block'
      document.getElementById('content').classList.remove('show')
      document.getElementById('error').style.display = 'none'
    }

    function hideLoading() {
      document.getElementById('loading').style.display = 'none'
      document.getElementById('content').classList.add('show')
    }

    function showError(error) {
      document.getElementById('loading').style.display = 'none'
      document.getElementById('content').classList.remove('show')
      document.getElementById('error').style.display = 'block'
      document.getElementById('error-message').textContent = error.message
    }

    function retryInitialization() {
      document.getElementById('error').style.display = 'none'
      initializeApp()
    }

    // Cleanup function
    function cleanupApp() {
      Object.values(app.components).forEach(component => {
        component?.unmount()
      })
      app.components = {}
    }

    // Initialize when DOM is ready
    document.addEventListener('DOMContentLoaded', initializeApp)

    // Cleanup on page unload
    window.addEventListener('beforeunload', cleanupApp)

    // Expose app to window for debugging
    window.formApp = app
  </script>
</body>
</html>

Configuration

CDN Options

Latest version (recommended for prototyping):

<script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>

Specific version (recommended for stability):

<script src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>

Alternative CDN (jsDelivr):

<script src="https://cdn.jsdelivr.net/npm/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>

Global Variable Access

The SDK is available via the window.TiroWebSDK global:

// Access all components
const {
  FormFiller,
  LaunchContextProvider,
  Narrative,
  ValidationFeedback,
  VisualizationToggle
} = window.TiroWebSDK

// Or access individually
const FormFiller = window.TiroWebSDK.FormFiller

Environment-Specific Configuration

Use different configurations for different environments:

const isDevelopment = window.location.hostname === 'localhost'

const config = {
  sdcEndpoint: {
    resourceType: 'Endpoint',
    address: isDevelopment
      ? 'https://dev-sdc.example.com/fhir'
      : 'https://prod-sdc.example.com/fhir'
  },
  visualize: isDevelopment
}

Best Practices

1. Version Pinning

For production, always pin to a specific version:

&lt;!-- ✅ Good - pinned version --&gt;
<script src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>

&lt;!-- ❌ Bad - unpredictable updates --&gt;
<script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>

2. Defer Script Loading

Use defer to avoid blocking page render:

<script defer src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>

3. Check Global Availability

Ensure SDK is loaded before using:

function initializeApp() {
  if (!window.TiroWebSDK) {
    console.error('Web SDK not loaded')
    return
  }

  const { FormFiller } = window.TiroWebSDK
  // ... proceed with initialization
}

4. Separate Configuration

Keep configuration separate from logic:

// Configuration object
const APP_CONFIG = {
  sdcEndpoint: {
    resourceType: 'Endpoint',
    address: 'https://your-sdc-backend.example.com/fhir'
  },
  questionnaireId: 'phq-9',
  visualize: false
}

// Use configuration
const filler = new FormFiller({
  questionnaire: APP_CONFIG.questionnaireId,
  sdcEndpoint: APP_CONFIG.sdcEndpoint,
  visualize: APP_CONFIG.visualize
})

5. Error Boundaries

Always handle initialization errors:

try {
  const filler = new FormFiller(config)
  filler.mount(container)
} catch (error) {
  console.error('Initialization failed:', error)
  showErrorMessage('Failed to load form. Please refresh the page.')
}

Limitations

Bundle Size

CDN builds include the entire library:

  • Larger file size compared to tree-shaken NPM builds
  • No code splitting or lazy loading
  • All dependencies included

Impact: Slower initial page load, especially on slow connections.

No TypeScript Support

CDN approach provides no type safety:

  • No autocompletion in editors
  • No compile-time error checking
  • Runtime errors only

Mitigation: Use JSDoc comments for basic type hints:

/**
 * @param {Object} config
 * @param {string} config.questionnaire
 * @param {Object} config.sdcEndpoint
 */
function createForm(config) {
  const filler = new FormFiller(config)
  return filler
}

Dependency Management

No package.json for version control:

  • Manual version updates required
  • No dependency lock files
  • Harder to track which version is in use

Mitigation: Document the version in comments:

&lt;!-- Tiro.health Web SDK v1.2.3 - Updated 2024-01-15 --&gt;
<script src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>

Security Updates

Manual updates required for security patches:

  • No automated security alerts
  • Must manually monitor for updates
  • Higher risk of running outdated code

Mitigation: Set a reminder to check for updates monthly.


Production Considerations

When CDN is Acceptable

  • Internal tools with controlled access
  • Low-traffic applications
  • Non-critical prototypes
  • Educational demos

When to Migrate to NPM

Consider migrating when:

  • Application becomes production-critical
  • Performance optimization is needed
  • Type safety becomes important
  • Team size grows (collaboration benefits)
  • Security requirements increase

Migration Path

When ready to migrate:

  1. Set up build tools: Install Node.js and a bundler (Vite, Webpack)
  2. Install via NPM: npm install @tiro-health/web-sdk
  3. Convert to modules: Use ES6 imports instead of global variable
  4. Add TypeScript: Optional but recommended for type safety
  5. Test thoroughly: Ensure all functionality works with new setup

See Vanilla JS Integration for a detailed migration guide.


Next Steps

For questions or support, please contact the Tiro.health team.

Was this page helpful?